(二)泛型学习笔记—泛型擦除原理

泛型的内部原理就是:类型擦除

java泛型被称为伪泛型,主要是因为在编译期间,所有的泛型信息都会被擦除掉。整个java泛型都是在编译器层次实现的。泛型基础知识里面测试过那个例子(colleage1和colleage2的类型相同),也是因为类型擦除的原因。

一、类型擦除

1.类型擦除:

使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉。这个过程就称为类型擦除。(生成的Java字节码中是不包含泛型中的类型信息的)

 栗子:

public static void  main(String[] args){
        ArrayList<String> arrayList1=new  ArrayList<String>();
        arrayList1.add("caka");
        ArrayList<Integer> arrayList2=new ArrayList<Integer>();
        arrayList2.add(100);
        System.out.println("arrayList1类型为:"+arrayList1.getClass());
        System.out.println("arrayList2类型为:"+arrayList2.getClass());
        System.out.println(arrayList1.getClass()==arrayList2.getClass());
    }

上面这段代码的输出结果是true,说明arrayList1和arrayList2的类型在运行的时候是相同的。泛型类型String和Integer都在编译的时候被擦除掉了,只剩下了原始类型。比如ArrayList<Integer>和ArrayList<String>等类型,在编译后都会变成ArrayList。JVM看到的只是ArrayList,泛型附加的类型信息对JVM来说是不可见的。

2.原始类型:

原始类型顾名思义就是没有泛型的最初类型,即擦除去了泛型信息,最后在字节码中的类型变量的真正类型。无论何时定义一个泛型类型,相应的原始类型都会被自动地提供。

没有限定类型时,类型变量被擦除,原始类型为Object。


class  Colleage<T>{  
    private  T student;  
      
    public Colleage(T student){  
        this.student=student;  
    }  
  
    public T getStudent() {  
        return student;  
    }  
  
    public void setStudent(T student) {  
        this.student = student;  
    }  
}  
上例中Colleage类在经过编译之后,就成为原始的Colleage类了,因为泛型形参T是一个无限定的类型变量,所以它的原始类型为Object。
有限定类型时,类型变量被擦除,原始类型是其限定类型。对于有多个限定的类型变量,那么原始类型就用第一个边界的类型变量来替换。

class  Colleage <T extends Comparable & Serializable>{

此时Colleage的原始类型就是Comparable。

class  Colleage <T extends Serializable & Comparable>{ 

此时Colleage的原始类型就是Serializable。编译器在必要的时要向Comparable插入强制类型转换。为了提高效率,应该将标签接口(即没有方法的接口)放在边界限定列表的末尾

3.原始类型和泛型变量的类型的区分

调用泛型方法的时候,可以指定泛型,也可以不指定泛型。原始类型和泛型变量的类型的区分也分为两种情况:

不指定泛型的情况下,泛型变量的类型为 该方法中的几种类型的同一个父类的最小级。

指定泛型的时候,该方法中的几种类型必须是该泛型实例类型或者其子类

public class Test{
    public static void main(String[] args){
        /**不指定泛型的时候*/
        int i=Test.add(1,2); //这两个参数都是Integer,所以T为Integer类型
        Number f=Test.add(1, 1.2);//这两个参数一个是Integer,一个是Float,所以取同一父类的最小级,为Number
        Object o=Test.add(1,"asd");//这两个参数一个是Integer,一个是String,所以取同一父类的最小级,为Object 
        /**指定泛型的时候*/
        int a=Test.<Integer>add(1,2);//指定了Integer,所以只能为Integer类型或者其子类
        Number c=Test.<Number>add(1,2.2); //指定为Number,所以可以为Integer和Float  
    }
    public static <T> T add(T x,T y){
            return y;  
    }  
}

4.泛型擦除疑问

先检查,后编译。
比如上例中加入这句代码:

int b=Test2.<Integer>add(1, 2.2)

就会出现编译错误,因为泛型是在编译之前就检查,检查到类型为Integer,所以2.2这个Double类型的就没法执行add。

泛型检查针对引用与该引用的对象无关

        ArrayList<String> arrayList1=new ArrayList();  
        arrayList1.add("1");//编译通过  
        arrayList1.add(1);//编译错误  
        String str1=arrayList1.get(0);//返回类型为String  
          
        ArrayList arrayList2=new ArrayList<String>();  
        arrayList2.add("1");//编译通过  
        arrayList2.add(1);//编译通过  
        Object object=arrayList2.get(0);//返回类型为Object 

上例中可以看出,类型检查是针对引用的,谁是一个引用,用这个引用调用泛型方法,就会对这个引用调用的方法进行类型检测,而无关它真正引用的对象。arrayList1是String类型的ArraryList的对象的引用。会对arrayList1的add方法进行类型检测,integer类型不能通过编译。arrayList2是一个ArraryList的对象,add方法可以添加String和Integer,此时对象类型为公共父类Object。

泛型中参数化类型不能继承,泛型中不允许引用传递

ArrayList<String> arrayList1=new ArrayList<Object>();//编译错误  
相当于用已经规定必须存放String类型的数组,可实际上已经被存放了Object类型的对象,这样,就会有ClassCastException了。所以为了避免这种极易出现的错误,Java不允许进行这样的引用传递。


问题:编译的时候进行类型擦除,那所有的泛型类型变量最后都会被替换为原始类型。并且获取的时候不需要手动的类型转换,那么类型转换是在什么时候进行的?

学习了一个厉害的大牛泛型讲解,以ArrayList的get方法的字节码文件为例,强制类型转换是在调用get()方法的时候进行的。

5.多态下的泛型(桥方法实现父类方法重写)

类型擦除后,父类的的泛型类型全部变为了原始类型Object。当在子类中给定类型时,重写了父类的方法,事实上子类中会存在方法的类型并不是父类的Object而是我们指定的类型。所以,JVM使用桥方法来完成多态。

桥方法的参数类型都是Object,也就是说,子类中真正覆盖父类方法的就是我们看不到的桥方法。桥方法的内部实现,就只是去调用我们自己重写的方法。

6.不能抛出也不能捕获泛型类的对象。因为异常都是在运行时捕获和抛出的,而在编译的时候,泛型信息全都会被擦除掉。

public class Problem<T> extends Exception{......}

在异常声明中可以使用类型变量。

public static<T extends Throwable> void doWork(T t) throws T{ 
不能声明参数化类型的数组
  Colleage<String>[] t = new Colleage <String>(10); //ERROR,擦除之后,t的类型为Colleage[]
泛型类中的静态方法和静态变量不可以使用泛型类所声明的泛型类型参数
 public class Test <T> {    
            public static T testNum;   //编译错误    
            public static T getValue(T testNum){ //编译错误    
                return null;    
            }    
        }  
因为泛型类中的泛型参数的实例化是在定义对象的时候指定的,而静态变量和静态方法不需要使用对象来调用。无法确定这个泛型参数是何种类型,所以会出现编译错误。(静态的泛型方法是没有问题的。)




发布了33 篇原创文章 · 获赞 51 · 访问量 9万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章