`

java泛型,原始类型,桥接方法

 
阅读更多
infoQ上的一篇JAVA泛型的文章,也不错。http://www.infoq.com/cn/articles/cf-java-generics
====================================================================================================
今天深入学习了下java泛型。看了coreJAVA中的泛型部分,然后网上看了些资料,发现这篇博客写的很好,表达很清楚。摘抄如下
原文链接: http://blog.sina.com.cn/s/blog_44c1e6da0100coxb.html

        Java泛型的本质是什么哪?虚拟机是如何对泛型进行处理的的那?
1.虚拟机中并没有泛型类型对象,所有的对象都是一样的,都属于普通的类。由于JVM根本不支持泛型类型,是编译器“耍了个花招”,使得似乎存在对泛型类型的支持―它们用泛型类型信息检查所有的代码,但随即“擦除”所有的泛型类型并生成只包含普通类型的类文件。泛型类在Java源码上看起来与一般的类不同,在执行时被虚拟机翻译成对应的“原始类型”。泛型类的类型参数列表被去掉,虚拟机用类型参数的限定类型对使用类型参数的地方进行了替换,如果没有限定类型则使用Object类型进行替换。这个过程就是所谓的“类型擦除”。类型参数如果有多个限定,则使用第一个限定类型做替换。泛型方法也会做相同的替换。
例如类Pair<T>
public classPair<T> {
    private Tfirst;
    private Tsecond;
    publicPair(T first, T second){
       this.first = first;
       this.second = second;
   }
    public voidsetFirst(T first){
       this.first = first;
   }
    public TgetFirst(){
       return first;
   }
    public voidsetSecond(T second){
       this.second = second;
   }
   
//   public void setSecond(Objectsecond){
//      this.second = (T) second;
//   }
    public TgetSecond(){
       return second;
   }
}
使用类分析器对其进行分析,结果:
public class Pair extendsjava.lang.Object{
   //域
    privatejava.lang.Object first;
    privatejava.lang.Object second;
   //构造器
    publicPair(java.lang.Object, java.lang.Object);
   //方法
    public voidsetFirst(java.lang.Object);
    public voidsetSecond(java.lang.Object);
    publicjava.lang.Object getSecond( );
    publicjava.lang.Object getFirst( );
}
如果将泛型类Pair的类型参数加上限定,比如Pair<T extendsComparable>,再使用类分析器对其进行分析,结果:
public class Pair extendsjava.lang.Object{
   //域
    privatejava.lang.Comparable first;
    privatejava.lang.Comparable second;
   //构造器
    publicPair(java.lang.Comparable,java.lang.Comparable);
   //方法
    public voidsetFirst(java.lang.Comparable);
    public voidsetSecond(java.lang.Comparable);
    publicjava.lang.Comparable getSecond( );
    publicjava.lang.Comparable getFirst( );
}
使用类型参数的限定进行了替换,这与预计的相同。
 
2.翻译泛型表达式:在程序调用泛型方法的时候,如果返回值被擦除,编译器会插入强制的类型转换。
如下两条语句
Pair<GregorianCalendar> birthdays =...;
GregorianCalendar first =birthdays.getFirst();
原始类型中方法getFirst()的返回被替换成Object,但是编译器会自动插入GregorianCalendar的强制类型转换。编译器会将这条语句翻译成两条虚拟机指令,并插入字节码:
  • 对原始方法getFirst()的调用;
  • 将返回的Object对象强制转换成GregorianCalendar。
当存取一个泛型域的时候也会在字节码中插入强制的类型转换。
 
3.翻译泛型方法:类型擦除同样发生在泛型方法中。例如之前我们定义的
虚拟机中同样也没有泛型方法,泛型方法也同样会经历“类型擦除”。例如,我们定义几个泛型方法:

public class ArrayAlg{
    publicstatic <T> T getMiddle(T[]t){
       System.out.println("泛型方法");
       return t[t.length/2];
   }
   
//  public static Object getMiddle(Object[]o){
//     return o[o.length/2];
//  }

    publicstatic <T extends Comparable> Tmin(T[] a){
       if(a == null || a.length == 0){
           return null;
       }
       T smallest = a[0];
       for(int i = 1;i <a.length;i++){
           if(smallest.compareTo(a[i]) >0){
               smallest = a[i];
           }
       }
       return smallest;
    }

   
    publicstatic <T extends Comparable>Pair<T> minmax(T[]ts){
       if(ts == null || ts.length == 0){
           return null;
       }
       T min = ts[0];
       T max = ts[0];
       for(int i = 0;i <ts.length;i++){
           if(min.compareTo(ts[i]) >0){
               min = ts[i];
           }
           if(max.compareTo(ts[i]) <0){
               max = ts[i];
           }
       }
       return new Pair<T>(min,max);
    }

   
//   public staticPair<Comparable> minmax(Comparable[]ca){
//      return null;
//   }

    publicstatic void main(String[] args) {
       String[] s = {"AAA","BBB","CCC"};
       System.out.println(ArrayAlg.<String>getMiddle(s));//在方法名前指定类型
//     System.out.println(<String>getMiddle(s));//不能这样用,虽然调用的是处在同一个类中静态方法,语法问题,<>不能加在方法名前
       Date[] d = {new Date(),new Date(),newDate()};
       System.out.println(getMiddle(d));//其实可以不指定参数,编译器有足够的信息推断出要调用的方法
       int[] is = {100,200,300};
       System.out.println(getMiddle(is));
   }
}

使用类分析器对其进行分析,结果:

public class ArrayAlg extendsjava.lang.Object{
   //方法
    publicstatic int getMiddle(int[]);
    publicstatic java.lang.ObjectgetMiddle(java.lang.Object[]);
    publicstatic Pair minmax(java.lang.Comparable[]);
    publicstatic void main(java.lang.String[]);
    publicstatic java.lang.Comparablemin(java.lang.Comparable[]);
}

 

泛型方法的类型擦除会带来两个问题1.类型擦除与多态的冲突;2.方法签名冲突

我们来看一个结构相对繁杂一些的类,类DateInterval继承前面定义的泛型类Pair<T>:

public class DateInterval extendsPair<Date> {
    publicDateInterval(Date first, Date second){
       super(first, second);
   }
   @Override
    public voidsetSecond(Date second) {
       super.setSecond(second);
   }
   @Override
    public DategetSecond(){
       return super.getSecond();
   }
    publicstatic void main(String[] args) {
       DateIntervalinterval = new DateInterval(new Date(), newDate());
       Pair<Date> pair =interval;//超类,多态
       Date date = new Date(2000, 1, 1);
       System.out.println("原来的日期:"+pair.getSecond());
       System.out.println("set进新日期:"+date);
       pair.setSecond(date);
       System.out.println("执行pair.setSecond(date)后的日期:"+pair.getSecond());

   }
}

我们知道Java中的方法调用采用的是动态绑定的方式,应该呈现出多态的特性。子类覆写超类中的方法,如果将子类向下转型成超类后,仍然可以调用覆写后的方法。但是泛型类的类型擦除造成了一个问题,Pair的原始类型中存在方法

public void setSecond(Objectsecond);

DateInterval中的方法

public void setSecond(Datesecond);

我们的本意是想覆写Pair中的setSecond方法,但是从方法签名上看,这完全是两个不同的方法,类型擦除与多态产生了冲突。而实际情况那?运行DateInterval的main方法,我们看到

public void setSecond(Datesecond)的确覆写了public void setSecond(Object second)方法。这是如何做到的那?

使用Java类分析器对其进行分析,结果:

public class DateInterval extendsPair{

   //构造器
    publicDateInterval(java.util.Date,java.util.Date);
   //方法
    public voidsetSecond(java.util.Date);
    public volatile voidsetSecond(java.lang.Object);//方法1
    public java.util.DategetSecond( );//方法2
    public volatilejava.lang.Object getSecond();//方法3,它难道不会和方法1冲突?
    publicstatic void main(java.lang.String[]);
}
方法1和方法3是我们在源码中不曾定义的,它肯定是由编译器生成的。这个方法称为桥方法(bridgemethod),真正覆写超类方法的是它。语句pair.setSecond(date)实际上调用的是方法1,publicvolatile void setSecond(Object),通过这个方法再去调用public voidsetSecond(Date)。这个桥方法的实际内容是:

public void setSecond(Objectsecond){

    this.setSecond((java.util.Date) second );

}

这样的结果就符合面向对象中多态的特性了,实现了方法的动态绑定。但是,这样的做法给我们带来了一种错觉,就认为public voidsetSecond(Date)覆写了泛型类的public voidsetSecond(Object),如果我们在DateInterval中增加一个方法:

    publicvoid setSecond(Object obj){
       System.out.println("覆写超类方法!");
   }

请再运行一次,观察这次的结果会有什么不同。有意思吧,我所使用的NetbeanIDE并没有提示我在这个方法前加上@Override,而它会提示你在setSecond(Date)方法前加上@Override的注释。现在我们知道了,这只是一个假象!
现在我们知道了方法3也是由编译器生成的桥方法,为了实现多态。方法擦除带来的第二个问题就是:由编译器生成的桥方法publicvolatile java.lang.Object getSecond()方法和public java.util.DategetSecond()方法,从方法签名的角度看是两个完全相同的方法,它们怎么可以共存那?如果是我们自己编写Java代码,这样的代码是无法通过编译器的检查的,但是虚拟机却是允许这样做的,因为虚拟机通过参数类型和返回类型来确定一个方法,所以编译器为了实现泛型的多态允许自己做这个看起来“不合法”的事情。
 
补充说明:从JDK1.5开始,在一个方法覆盖另一个方法时可以指定一个更严格的返回类型,它的机制也是同样使用的桥方法。例如:
public class A{
    public ListgetList(){
       return null;
   }
}
public class ASub extendsA{
   @Override
    publicArrayList getList(){
       return null;
   }
}
分析ASub类,结果:
public class ASub extendsA{
   //域
   //构造器
    public ASub();
   //方法
    publicjava.util.ArrayList getList( );
    publicvolatile java.util.List getList( );
}
分享到:
评论

相关推荐

    多图表实现员工满意度调查数据分析python

    员工满意度是指员工对于工作环境、待遇、职业发展和组织管理等方面的满意程度。它是衡量员工对工作的整体感受和情绪状态的重要指标。

    2020届软件工程本科毕业生毕业设计项目.zip

    2020届软件工程本科毕业生毕业设计项目

    基于stm32平衡小车

    平衡小车 基于stm32 平衡小车 基于stm32 平衡小车 基于stm32

    c语言火车票订票管理源码.rar

    c语言火车票订票管理源码.rar

    施耐德PLC例程源码四台水泵的轮换

    施耐德PLC例程源码四台水泵的轮换提取方式是百度网盘分享地址

    node-v16.13.2-linux-s390x.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    digix算法创新大赛2019baseline.zip

    digix算法创新大赛2019baseline

    三菱PLC小型立体仓库项目.zip

    三菱PLC小型立体仓库项目.zip 叉手篮具到位检测 入库2电机前限

    node-v15.3.0-linux-s390x.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    软考高项-ITTO背诵打印必过版-2024-高项已通过

    软考高项_ITTO背诵打印必过版_2024_高项已通过

    巧解HTTP三次握手四次挥手流程(超详细).docx

    TCP三次握手及四次挥手详细图解 相对于SOCKET开发者,TCP创建过程和链接折除过程是由TCP/IP协议栈自动创建的.因此开发者并不需要控制这个过程.但是对于理解TCP底层运作机制,相当有帮助. TCP三次握手 所谓三次握手(Three-way Handshake),是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。 三次握手的目的是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号并交换 TCP 窗口大小信息.在socket编程中,客户端执行connect()时。将触发三次握手。 1.TCP建立流程 第一次握手:建立连接时,客户端发送SYN(Seq = J)包到服务器,并进入到syn_sent状态。等待服务器确认。 第二次握手:服务器收到SYN包,知道了Client端想建立连接. 它会向客户端发送SYN+ ACK包(ack =J+1 TCP 四次挥手 TCP的连接的拆除需要发送四个包,因此称为四次挥手(four-way handshake)。客户端或服务器均可主动发起挥手动作,在socket编程中,任何一方执行close()操作即可产生挥手操作

    node-v13.5.0-linux-arm64.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    欧母龙PLC例程源码扎钢机程序

    欧母龙PLC例程源码扎钢机程序提取方式是百度网盘分享地址

    基于RGB相机的驾驶员注视区域估计_刘觅涵.pdf

    驾驶员注意力检测,驾驶员分神驾驶检测,DMS,汽车智能驾驶,智能座舱

    Unity实现二维码扫描,二维码生成

    Unity实现二维码扫描,二维码生成 提供ZXing.unity.dll可以进行二维码的生成和扫描,扫描的方式是在场景中发射射线,射线获取rawimage的texture并读取,然后作为二维码进行解析。 提供源代码。

    中山大学-计科-操作系统实验.zip

    中山大学-计科-操作系统实验.zip

    施耐德PLC例程源码恒压供水程序(用施耐德TWIDOPLC编的)

    施耐德PLC例程源码恒压供水程序(用施耐德TWIDO PLC编的)提取方式是百度网盘分享地址

    node-v14.8.0-darwin-x64.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    node-v14.6.0-darwin-x64.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    python实现小说词频统计

    小说词频统计是指对一本小说中出现的各个词语进行计数和分析,以确定每个词语在整篇小说中的出现频率。 以下是对小说词频统计的一些基本说明: 数据收集:首先需要获取目标小说的文本数据。这可以通过手动输入、复制粘贴文本内容或使用自动化工具来提取文本。 文本处理:在进行词频统计之前,通常需要对文本数据进行预处理。这可能包括去除标点符号、停用词(如“的”、“是”等)和特殊字符,将文本转换为统一的格式。 词语计数:进行词频统计时,遍历整个文本,逐个词语地计数它们的出现次数。通常会使用字典、哈希表或其他数据结构来存储词语及其计数。

Global site tag (gtag.js) - Google Analytics