如果使用泛型方法可以取代将整个类泛型化,那么就应该只使用泛型方法。另外,如果static方法需要使用泛型的能力,就必需使其成为泛型方法。
定义泛型方法的方式:将泛型参数列表置于返回值之前,如:
public void f(T x){
//...
}
在java se7以前,使用泛型时往往会出现如下的重复代码:
Map map = new HashMap>();
但是在java se7后,可以使用一个空的类型参数集合 <> 来代替重复的类型参数:
Map map = new HashMap<>();
java中泛型是使用擦除来实现的,使用泛型时,任何具体的类型信息都会被擦除。
虚拟机中没有泛型,只有普通的类和方法,所有参数类型都用它们的类型限定符替换。普通类会被替换为Object,List
因此,List
在泛型中,所有动作都发生在边界处:对传递进来的值进行编译期的类型检查,并插入对传递出去的值的转型。
由于擦除的存在,泛型代码中无法知道确切的类型信息,因此任何在运行时需要知道具体类型信息的操作都无法工作。但是有时可以引入类型标签来完成擦除的补偿:
public class A{
Class kind; //用一个Class类存储类型信息
public A(Class kind){
this.kind = kind;
}
public static void main(Stirng[] args){
A a = new A<>(String.class);
}
}
一个简单的泛型数组包装器:
public class TestArray {
Object array[];
public TestArray(int size) {
array = new Object[size];
}
@SuppressWarnings("unchecked")
public T get(int index){
return (T) array[index];
}
public void put(int index,T data){
array[index] = data;
}
public static void main(String[] args){
TestArray array = new TestArray<>(3);
array.put(0,"ABC");
System.out.println(array.get(0));
}
}
在这个示例中,数组的类型是Object[]而不是T[],当get()方法中返回数组元素时才转型为T。ArrayList的源码正是这样处理的,这样做比起直接将T[]作为数组类型更有一定优势。如果引入类型信息,就可以这样写:
public class TestArray {
T array[];
@SuppressWarnings("unchecked")
public TestArray(Class type,int size) {
array = (T[]) Array.newInstance(type,size);
}
public T get(int index){
return array[index];
}
public void put(int index,T data){
array[index] = data;
}
public static void main(String[] args){
TestArray array = new TestArray<>(String.class,3);
array.put(0,"ABC");
System.out.println(array.get(0));
}
}
这里直接根据传入的类型信息,使用Array.newTnstance()方法创建一个类型为T的数组。
其中构造函数的入参type类型为Class
由于擦除机制,参数类型T都会转换为Object类型,这意味着在泛型类的内部就不能直接调用T的方法(Object类没有的方法)。这时可以借助extends关键字设置参数类型的边界:
class B{
public void show(){
//...
}
}
public class A{
public void show(){
T t = new T();
t.show();
}
}
Java中,数组是可协变的。协变指的是,如果A类是B类的基类,那么A[]则是B[]的基类。
这种协变性会带来隐患,参考如下代码:
public class Test{
class A{}
class B extends A{}
class C extends A{}
public static void main(String[] args){
A[] a = new B[];
a[0] = new C(); //抛出ArrayStoreException异常
}
}
上述代码可以通过编译,因为A[]是B[]的基类,而A[]又是C[]的基类。但是运行时却会抛出一个异常,因为实际上a的类型是B[],而B[]并非C[]的基类。
泛型是不可协变性的,List
可以在泛型参数表达式中引入问号作为通配符,用法如下:
//存在Animal、Cat类,Cat继承Animal
List extends Animal> list = new ArrayList();
这里List extends Animal>意味着list持有的对象必须是某种具体的、基类为Animal的类。值得一提的是,这种方法定义的list,由于编译器并不知道“?”代表的到底是什么类型,如果你想用add()方法向其中添加元素,编译器会报错,因为这种添加行为并不安全。但是你依然可以调用list的get()方法来返回一个对象,因为 extends Animal>说明了容器里的对象至少是Animal类型。如下:
List list1 = new ArrayList<>();
list1.add(new Cat());
List extends Animal> list2 = list1;
//下一条语句无法通过编译
//list2.add(new Cat())
//get()方法可以使用
Animal a = list2.get(0);
另外一种用法是超类型通配符:List super Cat> list = new ArrayList