泛型
1,泛型问题引出
泛型是从JDK1.5之后追加到Java语言里面的,其主要目的是为了解决ClassCastException的问题,在进行对象的向下转型时永远都可能存在安全隐患,而Java希望可以通过泛型慢慢解决掉此类问题。
·泛型问题的引出
现在假设说定义一个描述x与y坐标的处理类,并且在这个类之中允许开发者保存有三类数据
·整型数据:x=10、y=20;
·浮点型数据:x=10.1、y=20.9;
·字符串型数据:x=东经120度、北纬30度。
于是在设计Point类的时候就需要去考虑具体的x和y属性的类型,这个类型要求可以保存上面三种数据类型,很明显最原始的做法就是利用Object类来进行定义,因为存在如下的转型关系:
·整型数据:基本数据类型→包装为Integer类对象→自动向上转型为Object;
·浮点型数据:基本数据类型→包装为Double类对象→自动向上转型为Object;
·字符串型数据:String类对象→自动向上转型为Object;
·范例:定义Point类如下
1 class Point{ 2 private Object x; 3 private Object y; 4 public void setX(Object x){ 5 this.x=x; 6 } 7 public void setY(Object y){ 8 this.y=y; 9 } 10 public Object getX(){ 11 return this.x; 12 } 13 public Object getY(){ 14 return this.y; 15 } 16 @Override 17 public String toString(){ 18 return "X:"+this.x+",Y:"+this.y; 19 } 20 }
·范例:进行正确的内容操作而后下面进行内容设置
1 public class Main { 2 public static void main(String[] args) { 3 Point point=new Point(); 4 //第一步:根据需求进行内容的设置 5 point.setX(10); //自动装箱,转成Object 6 point.setY(20); 7 //第二步:从里面获取数据 8 int x=(Integer)point.getX(); 9 int y=(Integer)point.getY(); 10 System.out.println(point.toString()); 11 System.out.println("x坐标:"+x+",y坐标:"+y); 12 } 13 }
·范例:观察安全隐患本程序之所以可以解决当前的设计问题,主要的原因在于,Object可以接受所有的数据类型,正因为如此所以本代码也会出现有严重的错误。
1 public class Main { 2 public static void main(String[] args) { 3 Point point=new Point(); 4 //第一步:根据需求进行内容的设置 5 point.setX(10); //自动装箱,转成Object 6 point.setY("北纬20度"); 7 //第二步:从里面获取数据 8 int x=(Integer)point.getX(); 9 int y=(Integer)point.getY(); 10 // System.out.println(point.toString()); 11 System.out.println("x坐标:"+x+",y坐标:"+y); 12 } 13 }
原始的开发过程中,人们只能使用Object类来统一参数,但是后来人们慢慢发现Object范围广并且转型有安全隐患,最好的方法就是不进行向下转型,所以这才有后面的泛型开发。此时的程序明显出现了问题,如果在程序编译的时候实际上是不会有任何的错误产生的,而程序执行的时候就会出现“ClassCastException”异常类型,所以本程序的设计是存在安全隐患的。而这个安全隐患存在的依据在于Object类可以涵盖的范围太广了,而对于这样的错误如果可以直接出现在编译的过程之中,那么就可以避免运行时的尴尬。
2,泛型基本定义
如果想要避免项目之中出现“ClassCastException”最好的做法就是直接回避掉对象的强制转换,所以在JDK1.5之后提供有泛型的技术,而泛型的本质在于,类中的属性或方法的参数与返回值的类型可以由对象实例化的时候动态决定。
那么此时就需要在类定义的时候明确的定义占位符(泛型标记)。
1 class Point<T>{//T是type的简写,可以定义多个泛型。 2 private T x; 3 private T y; 4 public void setX(T x){ 5 this.x=x; 6 } 7 public void setY(T y){ 8 this.y=y; 9 } 10 public T getX(){ 11 return this.x; 12 } 13 public T getY(){ 14 return this.y; 15 } 16 @Override 17 public String toString(){ 18 return "X:"+this.x+",Y:"+this.y; 19 } 20 }
如果后续实例化的时候没有跟着使用泛型,那么程序为了保证正确将会默认使用Object类来自动顶替泛型。此时Point类中的x与y属性的数据类型并不确定,而是由外部来决定。
提示:关于默认的泛型类型
·由于泛型是属于JDK1.5之后的产物,但是在这之前已经有不少内置的程序类或者是程序接口广泛的应用在项目开发之中。于是为了保证这些类或者接口追加了泛型之后,原始的程序类引燃可以使用,所以如果不设置泛型类型时,自动将使用Object类以保证程序的正常执行,但是在编译的过程之中会出现警告信息。
泛型定义完成后可以在实例化对象的时候进行泛型类型的设置,一旦设置之后,里面的x与y的属性类型就与当前对象直接绑定了。
1 public class Main { 2 public static void main(String[] args) { 3 Point<Integer> point=new Point<Integer>(); 4 //第一步:根据需求进行内容的设置 5 point.setX(10); //自动装箱,转成T 6 point.setY(20); 7 //第二步:从里面获取数据 8 int x=(Integer)point.getX(); 9 int y=(Integer)point.getY(); 10 System.out.println("x坐标:"+x+",y坐标:"+y); 11 } 12 }
泛型的使用注意点:现在的程序代码之中,由于Point类里面设置的泛型类型为Integer,这样所有的对应此泛型的属性、变量、方法返回值就将全部替换为Integer(只局限于此对象之中),这样在进行对象处理的时候如果发现设置的内容有错误,则会在程序编译的时候自动进行错误提示,同时也避免了对象的向下转型处理(可以避免我们说的安全隐患)。
·泛型之中只允许设置引用类型,如果现在要操作基本类型就必须使用包装类;
int属于基本类型,Integer才是属于包装类;同理double与Double、long与Long
·从JDK1.7开始,泛型对象实例化可以简化为:
【Point<Integer> point=new Point<>();】后面的包装类名称可以省略
使用泛型可以解决大部分类对象的强制转换处理,这样的程序才是一个合理的设计。
我们使用Object类接收全部数据时代已经是过去式了,是之前的代码,现在我们所有的设计应该更加严格泛型才是未来。
3,泛型通配符
虽然泛型帮助开发者解决了一系列的对象的强制转换所带来的的安全隐患,但是从另外一个角度来讲,泛型也带来了一些新的问题:引用传递处理。
·范例:观察问题的产生
1 public class Main { 2 public static void main(String[] args) { 3 Message<String> msg=new Message<String>(); 4 msg.setContent("www.cnblogs.com"); 5 fun(msg);//引用传递 6 } 7 public static void fun(Message<String> temp){ 8 System.out.println(temp.getContent()); 9 } 10 }
·范例:不设置泛型但是这个时候问题也就出现了,而问题的关键在于fun()方法上,如果真的去使用泛型,不可能只是一种类型,也就是说fun()方法应该可以接受任意种泛型类型的Message对象。但是这个时候它只能接收“Message<String>”类型,这种情况下有就有同学提出了,老师不设置泛型了。
1 public class Main { 2 public static void main(String[] args) { 3 Message<String> msgA=new Message<String>(); 4 Message<Integer> msgB=new Message<Integer>(); 5 msgA.setContent("www.cnblogs.com"); 6 fun(msgA);//引用传递 7 msgB.setContent(100); 8 fun(msgB);//引用传递 9 } 10 public static void fun(Message temp){ 11 System.out.println(temp.getContent()); 12 } 13 }
1 public static void fun(Message temp){ 2 temp.setContent(1.1); 3 System.out.println(temp.getContent()); 4 }
·范例:使用通配符如果不设置泛型,那么在方法之中就有可能对你的数据进行修改,所以此时我们需要找方案:可以接受所有的泛型类型,并且不能够修改里面的数据(允许获取),那么就需要通过通配符“?”来解决。
1 public static void fun(Message<?> temp){ 2 //temp.setContent(1.1); 3 System.out.println(temp.getContent()); 4 }
在“?”这个通配符的基础之上实际上还提供了有两类小的通配符:此时在fun()方法里面由于采用了Message结合通配符的处理,所以可以接受所有的类型,并且不允许修改只允许获取数据。
·?extends类:设置泛型的上限;
|-例如:定义“?extends Number”:表示该泛型类型只允许设置Number或者Number的子类
·?super类:设置泛型的下限:
|-例如:定义“?super String”:只能够使用String或其父类;
范例:观察泛型的上限配置
1 class Message<T extends Number>{ 2 private T content; 3 public void setContent(T content){ 4 this.content=content; 5 } 6 public T getContent() { 7 return this.content; 8 } 9 } 10 11 public class Main { 12 public static void main(String[] args) { 13 Message<Integer> msgB=new Message<Integer>(); 14 msgB.setContent(100); 15 fun(msgB);//引用传递 16 } 17 // public static void fun(Message<? extends Number> temp){ 18 public static void fun(Message<?> temp){ 19 System.out.println(temp.getContent()); 20 } 21 }
范例:设置泛型的下限配置
1 class Message<T>{ 2 private T content; 3 public void setContent(T content){ 4 this.content=content; 5 } 6 public T getContent() { 7 return this.content; 8 } 9 } 10 11 public class Main { 12 public static void main(String[] args) { 13 Message<String> msgA=new Message<String>(); 14 msgA.setContent("www.cnblogs.com"); 15 fun(msgA);//引用传递 16 } 17 18 public static void fun(Message<? super String> temp){ 19 System.out.println(temp.getContent()); 20 } 21 }
对于通配符而言是一个重要的概念,并且要求你一定可以理解此概念的定义,在日后学习Java一些系统类库的时候会见到大量的通配符使用。
4,泛型接口
泛型除了可以在类上定义之外也可以直接在接口之中进行使用,例如:下面定义一个泛型接口
1 interface IMessage<T>{ 2 public String echo(T t); 3 }
·实现方式一:在子类中继续设置泛型定义对于泛型接口的子类就有两种实现方式了。
1 interface IMessage<T>{ 2 public String echo(T t); 3 } 4 class MessageImpl<S> implements IMessage<S>{ 5 @Override 6 public String echo(S s) { 7 return "【echo】"+s; 8 } 9 } 10 public class Main { 11 public static void main(String[] args) { 12 IMessage<String> msg = new MessageImpl<>(); 13 System.out.println(msg.echo("这是一个子类实现泛型接口")); 14 } 15 }
·实现方式二:在子类实现父接口的时候直接定义出具体泛型类型
1 interface IMessage<T>{ 2 public String echo(T t); 3 } 4 5 class MessageImpl implements IMessage<String>{ 6 @Override 7 public String echo(String s) { 8 return "【echo】"+s; 9 } 10 } 11 12 public class Main { 13 public static void main(String[] args) { 14 IMessage<String> msg = new MessageImpl(); 15 System.out.println(msg.echo("这是一个子类实现泛型接口")); 16 } 17 }
如果从概念和实现上来讲并不复杂,但是在日后会遇见大量出现有泛型的接口,这个时候一定要清楚两种实现原则。
5,泛型方法
在之前的程序类里面实际上已经可以发现在泛型类之中如果将泛型标记写在方法上,那么这样的方法就称为泛型方法,但是需要注意的是,泛型方法不一定非要出现在泛型类之中,即:如果一个类上没有定义一个泛型方法,那么也可以使用泛型方法。
1 public class Main { 2 public static void main(String[] args) { 3 Integer[] num=fun(1,2,3,4);//传入整数,泛型类型就是Integer 4 for(int temp:num){ 5 System.out.println(temp); 6 } 7 } 8 public static <T> T[] fun(T ... args){ 9 10 //【<T>】对泛型方法中的对象类型进行标注 11 return args; 12 } 13 }
在后期进行项目开发的时候,这种泛型方法很常见,以之前的工厂设计为例。
如果此时一个项目有上千个接口,到时候满眼望去都是绝望的声影。
·范例:利用泛型改进工厂
1 interface IMessage{ 2 public void send(String str); 3 } 4 class MessageImpl implements IMessage{ 5 @Override 6 public void send(String str) { 7 System.out.println("【消息发送】:"+str); 8 } 9 } 10 class Factory{ 11 static <T> T getInstance(String className){ 12 if("messageImpl".equalsIgnoreCase(className)){ 13 return (T) new MessageImpl();//将对象进行转型 14 } 15 return null; 16 } 17 } 18 public class Main { 19 public static void main(String[] args) { 20 IMessage msg=(IMessage) Factory.getInstance("messageImpl"); 21 msg.send("这是一个范例方法"); 22 } 23 }