JAVA泛型理解

泛型类型的擦除:
 
       ArrayList<String> arrayList1=new ArrayList<String>();  
        ArrayList<Integer> arrayList2=new ArrayList<Integer>();  
        System.out.println(arrayList1.getClass()==arrayList2.getClass());//结果为true


说明泛型类型String和Integer都被擦除掉了,只剩下了原始类型。
试想在什么时候进行擦除的呢?是不是编译后进行擦除的?
           
    ArrayList<Integer> arrayList3=new ArrayList<Integer>();  
	        arrayList3.add(1);//这样调用add方法只能存储整形,因为泛型类型的实例为Integer  
	        arrayList3.getClass().getMethod("add", Object.class).invoke(arrayList3, "asd");  
	        for (int i=0;i<arrayList3.size();i++) {  
	            System.out.println(arrayList3.get(i));  
	        }  

运行结果可以看到,容器已经有1和asd,说明经过反射之后,可以往arrayList3放入字符串,
说明了Integer泛型实例在编译之后被擦除了,只保留了原始类型。

class Pair<T> {  
    private T value;  
    public T getValue() {  
        return value;  
    }  
    public void setValue(T  value) {  
        this.value = value;  
    }  
}

经过泛型擦除后的原始类型为:
class Pair {  
    private Object value;  
    public Object getValue() {  
        return value;  
    }  
    public void setValue(Object  value) {  
        this.value = value;  
    }  
} 


类型擦除引起的问题:
Java不能实现真正的泛型,只能使用类型擦除来实现伪泛型
先检查,在编译,以及检查编译的对象和引用传递的问题
既然说类型变量会在编译的时候擦除掉,那为什么我们往 arrayList中,不能使用add方法添加整形呢?不是说泛型变量Integer会在编译时候擦除变为原始类型Object吗,为什么不能存别的类型呢?既然类型擦除了,如何保证我们只能使用泛型变量限定的类型呢?

java编译器是通过先检查代码中泛型的类型,然后再进行类型擦除,在进行编译的。

public static  void main(String[] args) {  
        ArrayList<String> arrayList=new ArrayList<String>();  
        arrayList.add("123");  
        arrayList.add(123);//编译错误  
    }
 


 
public static void main(String[] args) {  
          
        //  
        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  
          
        new ArrayList<String>().add("11");//编译通过  
        new ArrayList<String>().add(22);//编译错误  
        String string=new ArrayList<String>().get(0);//返回类型就是String  
    }  


通过上面的例子,我们可以明白,类型检查就是针对引用的,谁是一个引用,用这个引用调用泛型方法,就会对这个引用调用的方法进行类型检测,而无关它真正引用的对象。

自动类型转换:
因为类型擦除的问题,所以所有的泛型类型变量最后都会被替换为原始类型。,既然都被替换为原始类型,那么为什么我们在获取的时候,
不需要进行强制类型转换呢?

public class Test {  
public static void main(String[] args) {  
ArrayList<Date> list=new ArrayList<Date>();  
list.add(new Date());  
Date myDate=list.get(0);  
}  



然后反编了下字节码,如下
public static void main(java.lang.String[]);  
Code:  
0: new #16 // class java/util/ArrayList  
3: dup  
4: invokespecial #18 // Method java/util/ArrayList."<init  
:()V  
7: astore_1  
8: aload_1  
9: new #19 // class java/util/Date  
12: dup  
13: invokespecial #21 // Method java/util/Date."<init>":()  
  
16: invokevirtual #22 // Method java/util/ArrayList.add:(L  
va/lang/Object;)Z  
19: pop  
20: aload_1  
21: iconst_0  
22: invokevirtual #26 // Method java/util/ArrayList.get:(I  
java/lang/Object;  
25: checkcast #19 // class java/util/Date  
28: astore_2  
29: return  


看第22 ,它调用的是ArrayList.get()方法,方法返回值是Object,说明类型擦除了。然后第25,它做了一个checkcast操作,即检查类型#19, 在在上面找#19引用的类型,他是
9: new #19 // class java/util/Date
是一个Date类型,即做Date类型的强转。
所以不是在get方法里强转的,是在你调用的地方强转的。


类型擦除与多态的冲突和解决方法:
class Pair<T> {  
    private T value;  
    public T getValue() {  
        return value;  
    }  
    public void setValue(T value) {  
        this.value = value;  
    }  
}  


然后我们想要一个子类继承它

class DateInter extends Pair<Date> {  
    @Override  
    public void setValue(Date value) {  
        super.setValue(value);  
    }  
    @Override  
    public Date getValue() {  
        return super.getValue();  
    }  
}  



在这个子类中,我们设定父类的泛型类型为Pair<Date>,在子类中,我们覆盖了父类的两个方法
分析:
实际上,类型擦除后,父类的的泛型类型全部变为了原始类型Object,所以父类编译之后会变成下面的样子:
class Pair {  
    private Object value;  
    public Object getValue() {  
        return value;  
    }  
    public void setValue(Object  value) {  
        this.value = value;  
    }  
}  

父类的类型是Object,而子类的类型是Date,参数类型不一样,这如果实在普通的继承关系中,根本就不会是重写,而是重载。

我们在一个main方法测试一下:

public static void main(String[] args) throws ClassNotFoundException {  
        DateInter dateInter=new DateInter();  
        dateInter.setValue(new Date());                  
                dateInter.setValue(new Object());//编译错误  
 }  


如果是重载不会出现编译错误,说明是重写了。
为什么会是这样呢?

虚拟机并不能将泛型类型变为Date,只能将类型擦除掉,变为原始类型Object。这样,我们的本意是进行重写,实现多态。可是类型擦除后,只能变为了重载。这样,类型擦除就和多态有了冲突。
JVM知道你的本意吗?知道!!!可是它能直接实现吗,不能!!!如果真的不能的话,那我们怎么去重写我们想要的Date类型参数的方法啊。

于是JVM采用了一个特殊的方法,来完成这项功能,那就是桥方法。


class com.tao.test.DateInter extends com.tao.test.Pair<java.util.Date> { 
  com.tao.test.DateInter(); 
    Code: 
       0: aload_0 
       1: invokespecial #8                  // Method com/tao/test/Pair."<init>" 
:()V 
       4: return 
 
  public void setValue(java.util.Date);  //我们重写的setValue方法 
    Code: 
       0: aload_0 
       1: aload_1 
       2: invokespecial #16                 // Method com/tao/test/Pair.setValue 
:(Ljava/lang/Object;)V 
       5: return 
 
  public java.util.Date getValue();    //我们重写的getValue方法 
    Code: 
       0: aload_0 
       1: invokespecial #23                 // Method com/tao/test/Pair.getValue 
:()Ljava/lang/Object; 
       4: checkcast     #26                 // class java/util/Date 
       7: areturn 
 
  public java.lang.Object getValue();     //编译时由编译器生成的巧方法 
    Code: 
       0: aload_0 
       1: invokevirtual #28                 // Method getValue:()Ljava/util/Date 去调用我们重写的getValue方法 

       4: areturn 
 
  public void setValue(java.lang.Object);   //编译时由编译器生成的巧方法 
    Code: 
       0: aload_0 
       1: aload_1 
       2: checkcast     #26                 // class java/util/Date 
       5: invokevirtual #30                 // Method setValue:(Ljava/util/Date;   去调用我们重写的setValue方法 
)V 
       8: return 



从编译的结果来看,我们本意重写setValue和getValue方法的子类,竟然有4个方法,其实不用惊奇,最后的两个方法,就是编译器自己生成的桥方法。
可以看到桥方法的参数类型都是Object,也就是说,子类中真正覆盖父类两个方法的就是这两个我们看不到的桥方法。而打在我们自己定义的setvalue和getValue方法上面的@Oveerride只不过是假象。
而桥方法的内部实现,就只是去调用我们自己重写的那两个方法。
所以,虚拟机巧妙的使用了巧方法,来解决了类型擦除和多态的冲突。


还有一点疑问:子类中的巧方法  Object   getValue()和Date getValue()是同 时存在的,可是如果是常规的两个方法,他们的方法签名是一样的,也就是说虚拟机根本不能分别这两个方法。
如果是我们自己编写Java代码,这样的代码是无法通过编译器的检查的,但是虚拟机却是允许这样做的,因为虚拟机通过参数类型和返回类型来确定一个方法,
所以编译器为了实现泛型的多态允许自己做这个看起来“不合法”的事情,然后交给虚拟器去区别。








 

猜你喜欢

转载自latty.iteye.com/blog/2351246