泛型
所谓泛型,就是允许在定义类、接口、方法时使用类型形参,这个类型形参将在声明变量、创建对象、调用方法时动态地指定。
集合中的泛型
泛型最常见在使用集合时,如:
List<String> list=new ArrayList<String>();
可以简写为:
List<String> list=new ArrayList<>();
泛型集合与泛型数组的不同
如果B是A的子类,对于数组而言,B[]
是A[]
的子类型,但是对于泛型,G<B>
不是G<A>
的子类型,下面第二行就会编译出错。
Object[] a=new String[10];
List<Object> a1=new ArrayList<String>();//编译错误
下面我们来探讨一下原因。
按照上面的逻辑,我们可以做下面实验:
Integer[] ia=new Integer[5];
Number[] na=ia;
na[0]=0.5;//编译正常,运行错误,有风险
上面的代码编译正常,但运行到na[0]=0.5;
会抛出一个ArrayStoreException异常,这是一种潜在的风险。
在Java的早期设计中,允许Integer[]数组赋值给Number[]变量存在缺陷,因此Java在泛型设计是进行了改进,它不再允许把List
List<Integer> iList=new ArrayList<>();
List<Number> nList=iList;
Java泛型设计的原则是,只要代码在编译时没有出现警告,就不会遇到运行时异常。
定义泛型接口、类
可以在任意类、接口的定义是增加泛型声明:
public class Apple <T>{
private T info;
public Apple(T info){
this.info=info;
}
}
当从泛型类中派生子类时,必须要指明参数类型。
public class Apple extends Fruit<String>
可以用如下方式声明一个泛型引用:
Fruit<String> fruit;
泛型方法
可以用如下方式定义泛型方法:
static <T> void fromArrayToCollection(T[] a, Collection<T> c){
for (T o:a){
c.add(o);
}
}
泛型构造器
和定义泛型方法一样:
class Foo{
<T> Foo(T t){
System.out.println(t);
}
}
使用如下:
Foo foo=new <String> Foo("hello");
通配符
类型通配符?
List<?>,这种带通配符的List表示它是各种泛型的父类,并不能把元素加入其中。例如,下面代码将会引起编译错误。
List<?> list=new ArrayList<>();
list.add(new Object());//编译错误
Object o=list1.get(0);
使用通配符的集合只能向其中取元素,不能往里面添加元素。
设定类型通配符的上限:? extends T
用法如下:
List<? extends Number> list3=new ArrayList<>();
list3.add(new Integer());//编译错误
Number str=list3.get(0);
这种方式声明的集合只能取元素,不能往里放元素,应为声明该集合时,只知道元素类型继承自Number,但不知道该集合中元素的具体类型,所以不能往里放元素;因为知道里面的元素一定是Number的子类,所以可以取出来
设定类型通配符的下限:? super T
用法如下:
List<? super Number> list4=new ArrayList<>();
list4.add(new Integer(1));
Object o1=list4.get(0);
这种方式声明集合时,可以往里面放元素,也可以从里面取元素。因为对于List<? super Number>
,可以知道集合元素声明的类型一定是Number的父类,所以往里面放Number及其子类的元素,而且取出来的元素一定是Object类型。
擦除与转换
在严格的泛型代码里,带泛型声明的类应该带着类型参数。但为了与老的Java代码保持一致,也允许在使用带泛型声明的类时不指定实际的类型参数。如果没有为这个泛型类指定实际的类型参数,则该类型参数被称作raw type(原始类型)。
当把一个具有泛型信息的对象赋值给另一个没有泛型信息的变量时,所有尖括号之间的类型信息都将被扔掉。比如一个List
class Orange<T extends Number>{
T size;
public Orange(){
}
public Orange(T size){
this.size=size;
}
public void setSize(T size){
this.size=size;
}
public T getSize(){
return this.size;
}
}
public class ErasureTest {
public static void main(String[] args){
Orange<Integer> a=new Orange<>(5);
Integer as=a.getSize();
//将a对象赋给b变量,将丢失尖括号里的类型信息
Orange b=a;
//b只知道size的类型为Number
Number size1=b.getSize();
//下面代码引起编译错误
Integer size2=b.getSize();
}
}
对泛型而言,可以直接将一个List
对象赋值给一个List<String>
对象,如下程序:
List<Integer> li=new ArrayList<>();
li.add(5);
li.add(9);
List list=li;
//下面代码引编译通过
List<String> ls=list;
//但从集合里取元素时会引起运行时异常
System.out.println(ls.get(0));
上面代码编译正常,但运行时会抛出ClassCastException异常,因为li变量声明的是List<String>
,实际引用的是List<Integer>
集合,所以当试图把该集合里的元素当成String类型的对象取出时,将引发运行时异常。