泛型简析——(二)类型擦除的限制

上一节简单地对泛型进行了介绍,我们差不多都对泛型有了一个大概的了解,这一节我们将会继续深入地去了解泛型的限制,了解它能做什么,不能做什么。
看下面一段代码:

List<Integer> list0 = new ArrayList<>();
List<String> list1 = new ArrayList<>();
System.out.println(list0.getClass()==list1.getClass());

可能有很多人会以为这肯定是false,因为两个List传入了两个不同的泛型参数进去,所以他们也就是持有不同类的集合,那么当然是false啦!

实际上运行结果为true~

这是因为在泛型代码在编译后都会将泛型的类型信息擦除,使得泛型类型变成一个原始类型,即去除类型参数后的泛型类型名。

接下来看另一段代码:

public class Test {
    public static void main(String[] args){
        Erasure<A> erasure = new Erasure<>(new A());
        System.out.println("erasure的类类型"+erasure.getClass());//输出erasure的类类型
        System.out.println("持有对象的类类型"+erasure.getT().getClass()); //输出持有的对象的类类型
    }
}
class Erasure<T>{
    private T t;
    public Erasure(T t){
        this.t = t;
    }
    public T getT() {
        return t;
    }
    public void setT(T t) {
        this.t = t;
    }
}
class A{
    void view(){
        System.out.println("太古蠢猪镜");
    }
}
//输出结果:
//      erasure的类类型class JavaBasic.Erasure
//      持有对象的类类型class JavaBasic.A

可以看到Erasure的类型参数被擦除了,变成了Erasure原始类型,而持有的对象A在取出后依然能确认其类型,因为在返回成员变量或直接对成员变量进行引用时会自动进行类型转换,如下所示:

public class Test {
    public static void main(String[] args){
        Erasure<A> erasure = new Erasure<>(new A());
        System.out.println("erasure的类类型:"+erasure.getClass());//输出erasure的类类型
        System.out.println("持有对象的类类型:"+erasure.getT().getClass()); //输出持有的对象的类类型,这里会进行类型转换

        Field[] fields = erasure.getClass().getDeclaredFields();
        for(Field f:fields){
            System.out.println("域名称:"+f.getName()+" ,域类型:"+f.getType()); //输出类的域信息
        }
        System.out.println("直接进行引用:"+erasure.t.getClass());//这里也会进行类型转换
    }
}
class Erasure<T>{
    public T t;
    public Erasure(T t){
        this.t = t;
    }
    public T getT() {
        return t;
    }
    public void setT(T t) {
        this.t = t;
    }
}
class A{
    void view(){
        System.out.println("太古蠢猪镜");
    }
}
//输出结果:
//      erasure的类类型:class JavaBasic.Erasure
//      持有对象的类类型:class JavaBasic.A
//      域名称:t ,域类型:class java.lang.Object
//      直接进行引用:class JavaBasic.A

可以看到,在取出t后会自动转换为我们需要的类型,而在泛型内部,t是一个Object的类型,因此在泛型内部无法做出与具体类型有关的操作:

class Erasure<T>{
    public T t;
    public Erasure(T t){
        this.t = t;
    }
    public T getT() {
        return t;
    }
    public void setT(T t) {
        this.t = t;
    }
    public void viewClassMsg(){
        //t.view();这里无法调用t的view方法,因为在泛型内部t是一个Object类的对象实例
    }
}

那我们想要进行与类型有关的操作要怎么办呢?

只需要对泛型参数进行限定即可:

public class Test {
    public static void main(String[] args){
        Erasure<B> erasure = new Erasure<>(new B());

        Field[] fields = erasure.getClass().getDeclaredFields();
        for(Field f:fields){
            System.out.println("域名称:"+f.getName()+" ,域类型:"+f.getType()); //输出类的域信息
        }
        erasure.viewClassMsg();
    }
}
class Erasure<T extends A>{//限定泛型参数
    public T t;
    public Erasure(T t){
        this.t = t;
    }
    public T getT() {
        return t;
    }
    public void setT(T t) {
        this.t = t;
    }
    public void viewClassMsg(){
        t.view();;
    }
}
class A{
    void view(){
        System.out.println("太古蠢猪镜");
    }
}
class B extends A{
    void view(){
        System.out.println("蠢海星");
    }
}
//输出结果:
//      域名称:t ,域类型:class JavaBasic.A
//      蠢海星

主动设置泛型的限定类型为A,泛型擦除后参数类型将被替换为限定类型,上述代码中设定泛型参数为B,B继承于A,泛型会将参数类型替换为限定类型A,因此可以在泛型内部执行与类型有关的view方法。

总结:

泛型有这这么多的限制,那么是不是就能说它是一个糟糕的功能呢?也不一定,泛型的出现是在Java 5之后,在此之前是没有泛型的,而为了对之前的代码作出兼容,就有了类型擦除,在编译后将泛型代码改变为非泛型代码,使得新旧代码之间能够相互兼容。这是一种妥协,为了让旧类库能迁移到现有Java泛型中的一种妥协。

你可能感兴趣的:(学习)