`
hxrs
  • 浏览: 26311 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

[转]Java JVM如何理解Java泛型类

阅读更多

/泛型代码
public class Pair<T>{
       private T first=null;
       private T second=null;
       public Pair(T fir,T sec){
            this.first=fir;
	    this.second=sec;
       }
       public T getFirst(){
             return this.first;
       }
       public T getSecond(){
             return this.second;
       }
       public void setFirst(T fir){
	     this.first=fir;
       }
}

 上面是一个很典型的泛型(generic)代码。T是类型变量,可以是任何引用类型。

1、Generic class 创建对象
            Pair<String> pair1=new Pair("string",1);           ...①
            Pair<String> pair2=new Pair<String>("string",1)    ...②
      有个很有趣的现象:  ①代码在编译期不会出错,②代码在编译期会检查出错误。
      这个问题其实很简单
      (1) Java编译器没有泛型对象这样的一个特殊概念。它会将所有泛型类的对象全部理解成为普通类对象(这一点会在下面详细阐述)。
      比如①,②两个代码编译器全部调用的是 Pair(Object fir, Object sec)这样的构造器。
      因此代码①中的new Pair("string",1)在编译器是没有问题的,毕竟编译器并不知道你创建的Pair类型中具体是哪一个类型变量T,而且编译器肯定了String对象和Integer对象都属于Object类型的。
       但是一段运行pair1.getSecond()就会抛出ClassCastException异常。这是因为JVM会根据第一个参数"string"推算出T类型变量是String类型,这样getSecond也应该是返回String类型,然后编译器已经默认了second的操作数是一个值为1的Integer类型。当然就不符合JVM的运行要求了,不终止程序才怪。
       (2) 但代码②会在编译器报错,是因为new Pair<String>("string",1)已经指明了创建对象pair2的类型变量T应该是String的。所以在编译期编译器就知道错误出在第二个参数Integer了。
       小结一下:
       创建泛型对象的时候,一定要指出类型变量T的具体类型。争取让编译器检查出错误,而不是留给JVM运行的时候抛出异常。

2、JVM如何理解泛型概念 —— 类型擦除
    事实上,JVM并不知道泛型,所有的泛型在编译阶段就已经被处理成了普通类和方法。
    处理方法很简单,我们叫做类型变量T的擦除(erased)
    无论我们如何定义一个泛型类型,相应的都会有一个原始类型被自动提供。原始类型的名字就是擦除类型参数的泛型类型的名字。
         如果泛型类型的类型变量没有限定(<T>) ,那么我们就用Object作为原始类型;
         如果有限定(<T extends XClass>),我们就XClass作为原始类型;
         如果有多个限定(<T extends XClass1&XClass2>),我们就用第一个边界的类型变量XClass1类作为原始类型;

    比如上面的Pair<T>例子,编译器会把它当成被Object原始类型替代的普通类来替代。

 //编译阶段:类型变量的擦除
public class Pair{
        private Object first=null;
        private Object second=null;
        public Pair(Object fir,Object sec){
            this.first=fir;
	    this.second=sec;
        }
       public Object getFirst(){
             return this.first;
       }
       public Object getSecond(){
             return this.second;
       }
       public void setFirst(Object fir){
	     this.first=fir;
       }
    }

3、泛型约束和局限性—— 类型擦除所带来的麻烦

(1)  继承泛型类型的多态麻烦。(—— 子类没有覆盖住父类的方法 )

     看看下面这个类SonPair

class SonPair extends Pair<String>{
          public void setFirst(String fir){....}
}

     很明显,程序员的本意是想在SonPair类中覆盖父类Pair<String>的setFirst(T fir)这个方法。但事实上,SonPair中的setFirst(String fir)方法根本没有覆盖住Pair<String>中的这个方法。
     原因很简单,Pair<String>在编译阶段已经被类型擦除为Pair了,它的setFirst方法变成了setFirst(Object fir)。 那么SonPair中 setFirst(String)当然无法覆盖住父类的setFirst(Object)了。

     这对于多态来说确实是个不小的麻烦,我们看看编译器是如何解决这个问题的。

     编译器 会自动在 SonPair中生成一个桥方法(bridge method )
           public void setSecond(Object sec){
                   setSecond((String) sec)
            }

      这样,SonPair的桥方法确实能够覆盖泛型父类的setFirst(Object) 了。而且桥方法内部其实调用的是子类字节的setSecond(String)方法。对于多态来说就没问题了。

      问题还没有完,多态中的方法覆盖是可以了,但是桥方法却带来了一个疑问:

      现在,假设 我们还想在 SonPair 中覆盖getSecond()方法呢?

class SonPair extends Pair<String>{
      public String getSecond(){....}
}

      由于需要桥方法来覆盖父类中的getSecond,编译器会自动在SonPair中生成一个 public Object getSecond()桥方法。
      但是,疑问来了,SonPair中出现了两个方法签名一样的方法(只是返回类型不同):

            ①String getSecond()   // 自己定义的方法

            ②Object getSecond()  //  编译器生成的桥方法
      难道,编译器允许出现方法签名相同的多个方法存在于一个类中吗?

      事实上有一个知识点可能大家都不知道:
      ① 方法签名 确实只有方法名+参数列表 。这毫无疑问!
      ② 我们绝对不能编写出方法签名一样的多个方法 。如果这样写程序,编译器是不会放过的。这也毫无疑问!
      ③ 最重要的一点是:JVM会用参数类型和返回类型来确定一个方法。 一旦编译器通过某种方式自己编译出方法签名一样的两个方法(只能编译器自己来创造这种奇迹,我们程序员却不能人为的编写这种代码)。JVM还是能够分清楚这些方法的,前提是需要返回类型不一样。

(2) 泛型类型中的方法冲突

       还是来看一段代码:

//在上面代码中加入equals方法
public class Pair<T>{
      public boolean equals(T value){
            return (first.equals(value)&&second.equals(value));
      }
}

       有谁会想到这样看似乎没有问题的代码连编译器都通过不了:

       【Error】    Name clash: The method equals(T) of type Pair<T> has the same erasure as equals(Object) of type Object but does not override it。

        编译器说你的方法与Object中的方法冲突了。


总结:泛型代码与JVM
    ① 虚拟机中没有泛型,只有普通类和方法。
    ② 在编译阶段,所有泛型类的类型参数都会被Object或者它们的限定边界来替换。(类型擦除)
    ③ 在继承泛型类型的时候,桥方法的合成是为了避免类型变量擦除所带来的多态灾难。


原帖: http://www.java3z.com/cwbwebhome/article/article8/822.html(感谢原作者!^_^)

分享到:
评论

相关推荐

    JVM如何理解Java泛型类.doc

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

    Java基础篇:泛型.pdf

    Java泛型的一个重要应用是在集合框架中。在JDK 1.5之前,Java集合类框架使用Object作为元素类型,这意味着集合可以存储任何类型的对象。然而,这种设计导致在从集合中获取元素时必须进行类型转换,这个过程是繁琐且...

    java泛型深入.pdf

    Java泛型深入的内容涵盖泛型的基本概念、泛型类、接口、方法以及泛型的使用限制和高级特性。 首先,Java中的泛型允许定义方法、接口、类和变量时不指定具体的数据类型,而是在使用的时候再通过泛型类型参数来指定...

    很好的Java泛型的总结

    Java泛型可以用于各种场景,例如集合、类、接口等。例如,定义一个List&lt;String&gt; strList = new ArrayList(),那么strList只能添加String类型的数据。 泛型的优点 ------------- Java泛型的优点是可以在编译期检查...

    利用java反射、注解及泛型模拟ORM实现

    2. 注解(Annotation):注解是Java提供的一种元数据,可以为编译器或JVM提供额外的信息。开发者可以在类、方法、变量等声明上添加注解,然后自定义注解处理器解析这些信息以执行特定的操作,比如代码生成、验证、...

    基于Java的泛型编程

    标题和描述均聚焦于“基于Java的泛型编程”,这一主题深入探讨了Java语言中泛型编程的概念、优点以及其实现机制。...理解并熟练掌握泛型编程,对于任何希望在Java开发中取得卓越成就的程序员来说,都是至关重要的技能。

    Java语言程序设计教程课件第五章泛型和枚举

    1. Java泛型是编译时特性,在JVM运行时不会保留泛型的类型信息。 2. Java泛型利用编译器擦除技术实现,这意味着泛型的源码在编译后会转化为非泛型版本的源码。 3. 泛型的类型擦除指的是在编译器前端处理阶段,去除...

    java泛型详解java泛型详解.doc

    Java 泛型是一种强大的语言特性,它允许在类、接口和方法中使用类型参数,以实现类型的安全性和代码的重用性。泛型是 Java 从 JDK 5.0 版本开始引入的重要特性,目的是增强类型系统,提高程序的类型安全性,减少运行...

    Java工程师必备面试题【多线程、反射、类加载器、JVM、泛型、异常处理、注解、面向对象编程、集合、IO流、序列化】

    内容概要:以上列出的Java面试题涵盖了Java语言的基础知识、面向对象编程、集合、IO流、多线程、反射、类加载器、JVM、序列化、泛型、异常处理、注解等多个方面。 适用人群:以上Java面试题适用于准备Java开发...

    CPlus_to_Java_Converter;C++转java工具

    转换器需要将模板转换为Java的泛型,但要注意Java泛型的边界和限制。 4. **异常处理**:C++的异常处理与Java的有所不同。转换器需要将C++的try-catch-finally块转换为Java的异常处理机制。 5. **内存管理**:C++...

    Java1.5泛型指南中文版

    除了定义泛型类和接口之外,Java 还支持定义泛型方法。泛型方法可以在非泛型类中定义,并且可以独立于类的类型参数。这种方法使得在不需要泛型类的情况下也可以利用泛型的灵活性。 #### 6. 与旧代码交互 在 Java ...

    Java泛型梳理.

    ### Java泛型梳理 #### 一、泛型概念解析 泛型是一种参数化的类型机制,其核心在于将具体的类型参数化处理。这种处理方式类似于函数中的变量参数——具体类型可以在定义时留空,而在实际使用时传入。这种方式极大...

    java泛型详解

    Java泛型的实现也带来了一些特性,比如泛型类没有自己独立的Class对象,所有的泛型类都共享一个Class对象,比如List.class。静态变量在泛型类的所有实例中是共享的,不论创建这些实例时使用的类型参数是什么。这意味...

    深入理解java泛型详解

    让我们深入探讨一下Java泛型的各个方面。 首先,泛型(Generic type)是一种在定义类、接口或方法时,使用类型参数的方式。类型参数就像是方法的形参,但用于类型而非值。例如,当我们创建一个泛型列表`List&lt;T&gt;`,`...

    Java泛型和集合-英文版

    根据提供的文件信息,我们可以确定本书的标题为《Java泛型和集合》(Java Generics and Collections),作者为Maurice Naftalin和Philip Wadler。该书详细介绍了Java中的泛型(Generics)特性以及集合(Collections...

    Java 泛型、collection接口.pdf

    System 类代表了当前 JVM 虚拟机对应的操作系统对象,可以拿系统时间、退出 JVM 虚拟机、复制数组等。 StringBuilder 类是一个字符串缓冲区,它可以用来进行字符串的拼接、删除、反转等操作。它比 String 类更高效...

    泛型实现小例

    由于Java虚拟机(JVM)不支持泛型,因此在编译后,所有的类型参数都会被替换为它们的边界类型或者Object,这个过程称为类型擦除。这意味着在运行时,我们无法通过反射或其他方式获取到泛型信息,但编译时的类型检查...

    Java软件开发实战 Java基础与案例开发详解 11-7 泛型概述 共8页.pdf

    ### Java泛型概述 #### 一、泛型概念与作用 **1.1 泛型的概念** 泛型是Java 5.0引入的新特性之一,它允许开发者在编写类或接口时使用一种不确定的类型(类型参数),并在使用时指定实际的类型。这类似于C++中的...

Global site tag (gtag.js) - Google Analytics