Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 576|回复: 0

[泛型学习]Java JVM如何理解Java泛型类

[复制链接]
  • TA的每日心情
    开心
    2021-3-12 23:18
  • 签到天数: 2 天

    [LV.1]初来乍到

    发表于 2014-10-29 23:56:32 | 显示全部楼层 |阅读模式
    /泛型代码
    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;
           }
    }
    [/code]  上面是一个很典型的泛型(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;
           }
        }[/code] 3、泛型约束和局限性―― 类型擦除所带来的麻烦 (1)  继承泛型类型的多态麻烦。(―― 子类没有覆盖住父类的方法 )       看看下面这个类SonPair class SonPair extends Pair<String>{
              public void setFirst(String fir){....}
    }[/code]      很明显,程序员的本意是想在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(){....}
    }[/code]       由于需要桥方法来覆盖父类中的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));
          }
    }[/code]        有谁会想到这样看似乎没有问题的代码连编译器都通过不了:        【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或者它们的限定边界来替换。(类型擦除)
         ③ 在继承泛型类型的时候,桥方法的合成是为了避免类型变量擦除所带来的多态灾难。



      
      
       
       

         
       

         
       
      
    复制代码
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2025-2-26 01:36 , Processed in 0.317198 second(s), 36 queries .

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

    快速回复 返回顶部 返回列表