java泛型—擦除的补偿

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的数组,类型信息全部丢失
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值