java泛型—擦除的补偿

1、为什么需要补偿

由于擦除,我们无法直接得到泛型参数的类型信息。所以任何运行时需要类型信息的操作都不能工作。

例如:

(1)无法使用instanceof关键字

(2)无法通过new操作创建一个泛型参数类型的对象

(3)无法创建泛型数组

public class Erase{
    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{
    class kind;    //使用Class对象
    public ClassTypeCapture(Class king){
        this.kind = kind;
    }
    public boolean f(Object arg){
        return kind.isInstance(arg);    //使用isInstance代替instanceof
    public static void main(String[] args){
        ClassTypeCapture ct = new ClassTypeCapture<>(B.class);
        ct.f(new B());    //true
        ct.f(new A());    //false
    }
}

(2)使用newInstance()方法创建对象

class ClassAsFactory{
    T x;
    public ClassAsFactory(Class kind){
        try{
            x = kind.newInstance();   
        }catch(Exception e){
            throw new RuntimeException()
        }
    }
}

但是对于没有默认构造器的类,无法用newInstance()来创建对象

对于没有默认构造器的类可以使用工厂方法模式

//工厂接口,强制工厂实现create方法,这个方法用来产生对象
interface FactoryI{
	T create();
}

//产生Integer对象的工厂,使用直接实现接口的方式来实现
class IntegerFactory implements FactoryI{
	public Integer create() {
		return new Integer(0);
	}
}

//使用内部类的方式达到对接口的实现
class Widget{
	public static class Factory implements FactoryI{
		public Widget create() {
			return new Widget();
		}
	}
}

//泛型类型通过接收一个工厂对象来创建T类型的对象
class Foo2{
	private T x;
	public > Foo2(F factory){
		x = factory.create();
	}
}

(3)创建泛型数组

创建泛型类型数组:

 如果是要创建泛型类型的数组,例如创建一个Generic类型的数组(也就是创建Generic[]),那么唯一的办法时创建擦除类型的数组然后转型:

Generic[] gia =(Generic) new Generic[10];

创建泛型类型参数的数组:

一般情况如果需要用到泛型参数类型的数组那么使用ArrayList。

例如:

public class ListOfGenerics{
    private List 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{
    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 gia = new GenericArray<>(10);
        gia.put(0,1);//可以使用
        Integer i = gia.get(0);//可以使用
        //Integer[] ia = gia.rep();//运行错误
        Object[] oa = gia.rep();//可以使用
}

另一种是使用Class对象,这是最好的方式,里面的方法都能很好的使用。

class GenericArrayWithTypeToken{
	private T[] array;
	public GenericArrayWithTypeToken(Class 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{
    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 gia = new GenericArrays<>(10);
        //Integer[] ia = gia.rep(); 运行错误
        gia.put(0,1);//可以使用
        Integer i = gia.get(0);//可以使用
        Object[] oa = gai.rep();//仅能返回一个Object的数组,类型信息全部丢失
    }
}

 

你可能感兴趣的:(java基础)