`

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( );
}
分享到:
评论

相关推荐

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

    本文详细介绍了java泛型、原始类型、桥接方法。

    泛型,泛型擦除,桥接方法

    泛型,泛型擦除,桥接方法

    java 泛型类的类型识别示例

    java 泛型类的类型识别示例 java 泛型类的类型识别示例 java 泛型类的类型识别示例

    java 泛型方法使用示例

    java 泛型方法使用示例 java 泛型方法使用示例 java 泛型方法使用示例

    Java泛型编程指南.pdf

    Java泛型编程指南.pdf 此文章译自SUN的泛型编程指南

    Java泛型和集合

    Java Generics and Collections 英文版,详细描述java 泛型技术

    JAVA泛型加减乘除

    这是一个使用JAVA实现的泛型编程,分为两部分,第一部分创建泛型类,并实例化泛型对象,得出相加结果。 第二部分用户自行输入0--4,选择要进行的加减乘除运算或退出,再输入要进行运算的两个数,并返回运算结果及...

    java 泛型接口示例

    java 泛型接口示例 java 泛型接口示例 java 泛型接口示例

    1.java泛型定义.zip

    1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1....

    java泛型总结

    深入理解java泛型,包括类名泛型的定义,方法泛型定义,泛型的返回

    java泛型技术之发展

    java泛型技术之发展,学习JAVA 泛型的不错东东

    4.java泛型的限制.zip

    4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip...

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

    Java中的泛型,在运行时刻其具体类型是被擦除的,这样我们就不能用new T(),instanceof等关操作,特别是对泛型类型的类的实例化问题,在此根据《Thinking in Java》中所讲的对类型擦除所带来问题的三种解决方案,比较...

    Java泛型的用法及T.class的获取过程解析

    主要介绍了Java泛型的用法及T.class的获取过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

    java泛型学习ppt

    java,学习java泛型,java培训之泛型.pptxjava培训之泛型.pptxjava培训之泛型.pptxjava培训之泛型.pptx

    很好的Java泛型的总结

    很好的Java泛型的总结,看完之后你一定会知道java泛型的底层机制,你一定会学会Java泛型!

    JVM如何理解Java泛型类.doc

    JVM如何理解Java泛型类.doc JVM如何理解Java泛型类.doc

    java泛型详解.pdf

    java泛型详解.pdf

    Java泛型实例

    Java泛型,泛型接口、泛型方法实例

    Java泛型应用实例

    网上很多讲泛型原理的文章, 很少有提到我们什么时候使用泛型有优势, 我就泛型类和方法搞了两个实例,算是探索下泛型使用的冰山一角吧,在我博客里面有配套的文章,可以配套看.

Global site tag (gtag.js) - Google Analytics