`

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代码,这样的代码是无法通过编译器的检查的,但是虚拟机却是允许这样做的,因为虚拟机通过参数类型和返回类型来确定一个方法,
所以编译器为了实现泛型的多态允许自己做这个看起来“不合法”的事情,然后交给虚拟器去区别。








 
分享到:
评论

相关推荐

    Java泛型三篇文章,让你彻底理解泛型(super ,extend等区别)

    Java 泛型详解 Java 泛型是 Java SE 5.0 中引入的一项特征,它允许程序员在编译时检查类型安全,从而减少了 runtime 错误的可能性。泛型的主要优点是可以Reusable Code,让程序员编写更加灵活和可维护的代码。 ...

    Java泛型应用实例

    Java泛型是Java编程语言中的一个强大特性,它允许我们在定义类、接口和方法时指定类型参数,从而实现代码的重用和类型安全。在Java泛型应用实例中,我们可以看到泛型如何帮助我们提高代码的灵活性和效率,减少运行时...

    java泛型技术之发展

    Java泛型是Java编程语言中的一个关键特性,它在2004年随着Java SE 5.0的发布而引入,极大地增强了代码的类型安全性和重用性。本篇文章将深入探讨Java泛型的发展历程、核心概念以及其在实际开发中的应用。 1. **发展...

    java 泛型接口示例

    下面我们将详细探讨Java泛型接口的相关知识点。 1. **泛型接口的定义** 泛型接口的定义方式与普通接口类似,只是在接口名之后添加了尖括号`&lt;T&gt;`,其中`T`是一个类型参数,代表某种未知的数据类型。例如: ```java...

    java 泛型方法使用示例

    下面我们将深入探讨Java泛型方法的概念、语法以及使用示例。 **一、泛型方法概念** 泛型方法是一种具有类型参数的方法,这些类型参数可以在方法声明时指定,并在方法体内部使用。与类的泛型类似,它们提供了编译时...

    java 泛型类的类型识别示例

    在Java编程语言中,泛型(Generics)是一种强大的特性,它允许我们在编写代码时指定容器(如集合)可以存储的数据类型。这提高了代码的安全性和效率...通过学习和理解这些示例,你可以更好地掌握Java泛型类的类型识别。

    java泛型的内部原理及更深应用

    通过学习这些知识点,开发者能更好地理解Java泛型的内部原理,以及如何在实际项目中充分利用泛型的特性,提高代码质量和安全性。在这个视频教程中,张孝祥老师将详细讲解这些概念,并通过实例帮助学员深入掌握Java...

    SUN公司Java泛型编程文档

    SUN公司的Java泛型编程文档,包括英文原版和网络翻译版,为开发者提供了深入理解和熟练运用泛型的重要资源。 首先,泛型的基本概念是允许在定义类、接口和方法时使用类型参数,这样就可以在编译时检查类型安全,...

    JVM如何理解Java泛型类.doc

    ### JVM如何理解Java泛型类 #### 一、引言 在Java中,泛型是一种强大的功能,它允许程序员编写灵活且类型安全的代码。然而,对于Java虚拟机(JVM)来说,它实际上并不理解泛型的概念。所有的泛型信息在编译阶段就被...

    java泛型深入.pdf

    总之,深入理解Java泛型能够帮助开发者编写更安全、更健壮的代码。通过掌握泛型的原理和细节,开发者可以有效利用泛型来优化代码设计,提升程序的类型安全,同时在维持程序性能的同时简化代码。

    Java泛型技术之发展.pdf

    通过理解并熟练运用Java泛型,开发者可以编写出更加健壮、类型安全且易于维护的代码,提升软件质量。Java泛型技术的不断发展和完善,也为Java程序员提供了强大的工具来应对复杂的数据处理场景。

    Java 泛型擦除后的三种补救方法

    Java 泛型是一种强大的工具,它允许我们在编程时指定变量的类型,提供了编译时的类型安全。然而,Java 的泛型在运行时是被擦除的,这意味着在运行...理解并掌握这些设计模式对于编写健壮、可维护的Java代码至关重要。

    关于C#、java泛型的看法

    本文将深入探讨C#和Java在泛型实现上的异同,帮助开发者更好地理解和利用这两种语言的泛型功能。 首先,我们来看C#中的泛型。C#自2.0版本开始引入泛型,它允许开发者在类、接口和方法中定义类型参数,从而创建可...

    java泛型Demo

    Java泛型是Java编程语言中的一个关键特性,它在2004年随着JDK 5.0的发布被引入。泛型的主要目的是提供类型安全,帮助程序员在编译时发现...在这个"java泛型Demo"中,你将有机会实践这些概念,加深对Java泛型的理解。

    Java泛型技术之发展

    Java泛型技术的发展历程及其在JDK1.4中的实现,是IT领域特别是软件开发与设计中的一个重要里程碑。本文将深入探讨泛型技术的概念、历史背景、与其它编程概念的区别,以及其在Java语言中的具体应用。 ### 泛型技术...

Global site tag (gtag.js) - Google Analytics