7. 泛型
泛型(generic)是指参数化类型的能力,泛型的本质也是将数据类型也参数化。可以定义泛型类型的类或方法。随后编译器会用具体的类型来替换它。使用泛型的主要优点是能够在编译时,而不是运行时检测出错误,从而提高软件的可靠性和可读性。
泛型类,接口和泛型方法:
<T><E>表示泛型类型(formal generictype),随后可以用一个实际具体类型(concrete type)来替换他。替换泛型类型称为泛型实例化(generic instantiation)。
定义一个类为泛型类型,需要将泛型类型放在类名之后。如:public class GenericStack<E> ;
泛型类的构造方法应该被定义为publicClassName() ,而不是public ClassName<E>();泛型类多个参数时,可以写成<E1,E2,E3>;
泛型方法定义时:修饰符 <E>返回类型方法名(),泛型类型放在方法返回类型之前。
如:public static <E extends Comparable<E>> E max(E a , E b ); 这就是可以将泛型指定为另一种类型的子类型。
调用泛型方法时:需要将实际类型放在尖括号内作为方法名的前缀。如:
Object.<Integer>print(integer);
不使用类型参数的泛型类型称为原始类型(rawtype)。比如:ArrayList li = new ArrayList()。但是原始类型是不安全的,因为编译阶段没有出错,但是运行阶段有可能会出错。
通配符:
ClassName<Integer>并不是ClassName<Number>的子类,为了避免出现这个问题,出现了通配泛型类型。有三种:?、 ? extends T 和 ? super T;
<? Extends Number> 表示的是Number和Number的子类型的通配类型。
下面的例子有点意思,两种编译都正确:
public class WildCardDemo3 {
public static void main(String[] args) {
GenericStack<String> stack1 = new GenericStack<String>();
GenericStack<Object> stack2 = new GenericStack<Object>();
stack2.push("Java");
stack2.push(2);
stack1.push("Sun");
add(stack1, stack2);
WildCardDemo2.print(stack2);
}
第一种:
public static <T> void add(GenericStack< ? extends T > stack1,
GenericStack<T> stack2) {
while (!stack1.isEmpty())
stack2.push(stack1.pop());
}
第二种:
public static <T> void add(GenericStack< T > stack1,
GenericStack<? super T> stack2) {
while (!stack1.isEmpty())
stack2.push(stack1.pop());
}
}
消除泛型:
泛型类型使用类型消除(type erasure)的方法来实现。编译器使用泛型类型来编译代码,但是随后消除它。所以泛型在运行时是不可用的。
泛型存在于编译阶段,一旦编译器确认泛型类型是安全的,就会将它转换为原始类型。比如:
ArrayList<String>list = new ArrayList<String>();
list.add(“hahah”);
String s= list.get(0);
转换成:
ArrayListlist = new ArrayList();
list.add(“hahah”);
String s= (String)list.get(0);
当编译泛型类、接口和方法时,编译器会用Object类型代替泛型类型。
如果一个类型是不受限的,那么编译器就会用一个受限类型来替换它。如:
publicstatic <E extends Number> void print();
替换成:
publicstatic <Number> void print();
泛型类会出现运行时被消除,需要注意的是:
1. 不管实际的具体类型是什么,泛型类是被他所有的实例所共享的。虽然会创建出不同类型的实例,但运行时只有一个类会被加载到JVM中的。表述如下是错误的:
listinstanceof Object<String>是错误的,因为Java虚拟机中并没有单独存储这个Object<String>类,只有Object类。
2. 不能是有泛型类型来创建实例。 不能使用new E();
3. 不能使用泛型类型来创建数组。如new E[];但是可以通过类型转换来规避这个限制。如:
E[] ll = (E[])new Object[10];这种形式是允许的。
4. *在静态环境下不允许类的参数是泛型类型。这是由于泛型类的所有实例都有相同的运行时类。所以泛型类的静态变量和方法都是被它的所有实例所共享的。因此在静态方法、数据域或者初始化语句中,为了类而引用泛型类型参数是非法的*。
5. *异常类不能是泛型的*。