1、为什么需要补偿
由于擦除,我们无法直接得到泛型参数的类型信息。所以任何运行时需要类型信息的操作都不能工作。
例如:
(1)无法使用instanceof关键字
(2)无法通过new操作创建一个泛型参数类型的对象
(3)无法创建泛型数组
public class Erase<T>{
public static void f(Object arg){
if(arg instanceof T){}//编译错误
T var = new T();//编译错误,这里不能直接创建对象的原因还有无法确认T有默认的构造器
T[] array = new T[10];//编译错误
}
}
2、如何补偿(如何获得丢失的类型信息)
解决方法之一是可以使用Class对象来获得类型信息。
例如:
(1)使用动态的isInstance代替instanceof
class A{}
class B extends A{}
public class ClassTypeCapture<T>{
class<T> kind; //使用Class对象
public ClassTypeCapture(Class<T> king){
this.kind = kind;
}
public boolean f(Object arg){
return kind.isInstance(arg); //使用isInstance代替instanceof
public static void main(String[] args){
ClassTypeCapture<B> ct = new ClassTypeCapture<>(B.class);
ct.f(new B()); //true
ct.f(new A()); //false
}
}
(2)使用newInstance()方法创建对象
class ClassAsFactory<T>{
T x;
public ClassAsFactory(Class<T> kind){
try{
x = kind.newInstance();
}catch(Exception e){
throw new RuntimeException()
}
}
}
但是对于没有默认构造器的类,无法用newInstance()来创建对象
对于没有默认构造器的类可以使用工厂方法模式
//工厂接口,强制工厂实现create方法,这个方法用来产生对象
interface FactoryI<T>{
T create();
}
//产生Integer对象的工厂,使用直接实现接口的方式来实现
class IntegerFactory implements FactoryI<Integer>{
public Integer create() {
return new Integer(0);
}
}
//使用内部类的方式达到对接口的实现
class Widget{
public static class Factory implements FactoryI<Widget>{
public Widget create() {
return new Widget();
}
}
}
//泛型类型通过接收一个工厂对象来创建T类型的对象
class Foo2<T>{
private T x;
public <F extends FactoryI<T>> Foo2(F factory){
x = factory.create();
}
}
(3)创建泛型数组
创建泛型类型数组:
如果是要创建泛型类型的数组,例如创建一个Generic<Integer>类型的数组(也就是创建Generic<Integer>[]),那么唯一的办法时创建擦除类型的数组然后转型:
Generic<Integer>[] gia =(Generic<Integer>) new Generic[10];
创建泛型类型参数的数组:
一般情况如果需要用到泛型参数类型的数组那么使用ArrayList。
例如:
public class ListOfGenerics<T>{
private List<T> array = new ArrayList<>();
public void add(T item){array.add(item);}
public T get(int index){return array.get(index);}
}
除了ArrayList还有下面三种方法:
一是使用Object数组,在返回时进行转型,依然无法返回一个T类型的数组,但是可以返回T类型的单个元素值。
class GenericArray<T>{
private Object[] array;
public GenericArray(int size){
array = new Object[size];
}
public void put(int index,T item){ Object[index]=item;}
//在返回时进行转型
public T get(int index){ return (T)Object[index];}
//这个即使转型返回的依然是Object[]
public T[] rep(){return (T[])array;}
public static void main(String[] args){
GenericArray<Integer> gia = new GenericArray<>(10);
gia.put(0,1);//可以使用
Integer i = gia.get(0);//可以使用
//Integer[] ia = gia.rep();//运行错误
Object[] oa = gia.rep();//可以使用
}
另一种是使用Class对象,这是最好的方式,里面的方法都能很好的使用。
class GenericArrayWithTypeToken<T>{
private T[] array;
public GenericArrayWithTypeToken(Class<T> type,int size) {
array = (T[])Array.newInstance(type, size);//产生警告的地方
}
public void put(int index,T t) {
array[index] = t;
}
public T get(int index) {
return array[index];
}
public T[] rap() {
return array;
}
}
这种方法关键就是使用Class对象和Array.newInstance()方法,上面实例中的代码会有警告,警告内容是Object[]转换成T[]可能会不安全,也就是说Array.newInstance()返回的应该是个Object[],那么能不能直接在array初始化时使用Object[]然后转型为T[]呢?答案是否定的。可以看到Array.newInstance()方法中有一个参数是Class对象,也就是说它可以使用Class对象来恢复被擦除的信息,而直接使用Object[]然后转型也无法返回确切类型的数组。
例子:
public class GenericArrays<T>{
private T[] array;
public GenericArrays(int size){
array = (T[]) new Object[size];//这里的警告与上面一样
}
public void put(int index,T item) { array[index]=item;}
//
public T get(int index) { return array[index];}
public T[] rep() { return array;}
public static void main(String[] args){
GenericArrays<Integer> gia = new GenericArrays<>(10);
//Integer[] ia = gia.rep(); 运行错误
gia.put(0,1);//可以使用
Integer i = gia.get(0);//可以使用
Object[] oa = gai.rep();//仅能返回一个Object的数组,类型信息全部丢失
}
}