`
towne
  • 浏览: 37506 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
文章分类
社区版块
存档分类
最新评论
  • mapeijie888: 谢谢 能不能把  此文相关的代码发至邮箱  mapeijie8 ...
    mock测试
  • towne: 如Calendar,Date等类型则需要用户自己实现Conve ...
    XStream
  • jinkingmanager: 讲的挺好,不过我有些问题想问一下: 1 如果一个XML中对应有 ...
    XStream
  • towne: 在web项目中测试servlet比较麻烦,像servletRe ...
    mock测试

泛型的本质

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

    public static <T extends Comparable> T min(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;
    }

   
    public static <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 static Pair<Comparable> minmax(Comparable[] ca){
//       return null;
//   }

    public static 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(),new Date()};
        System.out.println(getMiddle(d));//其实可以不指定参数,编译器有足够的信息推断出要调用的方法
        int[] is = {100,200,300};
        System.out.println(getMiddle(is));
    }
}

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

public class ArrayAlg extends java.lang.Object{
    //方法
    public static int getMiddle(int[]);
    public static java.lang.Object getMiddle(java.lang.Object[]);
    public static Pair minmax(java.lang.Comparable[]);
    public static void main(java.lang.String[]);
    public static java.lang.Comparable min(java.lang.Comparable[]);
}

 

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

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

public class DateInterval extends Pair<Date> {
    public DateInterval(Date first, Date second){
        super(first, second);
    }
    @Override
    public void setSecond(Date second) {
        super.setSecond(second);
    }
    @Override
    public Date getSecond(){
        return super.getSecond();
    }
    public static void main(String[] args) {
        DateInterval interval = new DateInterval(new Date(), new Date());
        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(Object second);

 

DateInterval中的方法

public void setSecond(Date second);

 

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

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

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

public class DateInterval extends Pair{

    //构造器
    public DateInterval(java.util.Date, java.util.Date);
    //方法
    public void setSecond(java.util.Date);
    public volatile void setSecond(java.lang.Object);//方法1
    public java.util.Date getSecond( );//方法2
    public volatile java.lang.Object getSecond( );//方法3,它难道不会和方法1冲突?
    public static void main(java.lang.String[]);
}

 方法1和方法3是我们在源码中不曾定义的,它肯定是由编译器生成的。这个方法称为桥方法(bridge method),真正覆写超类方法的是它。语句pair.setSecond(date)实际上调用的是方法1,public volatile void setSecond(Object),通过这个方法再去调用public void setSecond(Date)。这个桥方法的实际内容是:

public void setSecond(Object second){

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

}

 

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

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

 

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

相关推荐

    Generic_2(泛型类-泛型方法-泛型接口-泛型限定(上限)

    泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。 Java语言引入泛型的好处是...

    Generic_3(泛型限定(下限)-泛型限定(上限的体现)-泛型限定(下限的体现)-泛型限定(通配符的体现)-集合查阅的技巧)

    泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。 Java语言引入泛型的好处是...

    JAVA-泛型基本讲解

    1、Java泛型是J2 SE1.5中引入的一个新特性,其本质是参数化类型,也就是说所操作的数据类型被指定为一个参数(type parameter)这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。...

    java_泛型类相关介绍.docjava_泛型类相关介绍.doc

    泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。  Java语言引入泛型的好处是...

    java泛型文档

    对Java泛型的描述,何谓泛型呢?通俗的说,就是泛泛的指定对象所操作的类型,而不...泛型的本质就是将所操作的数据类型参数化,也就是说,该数据类型被指定为一个参数。这种参数类型可以使用在类、接口以及方法定义中。

    泛型generics.zip

    泛型(Generics)是JDK5引入的一种参数化类型...其本质是参数类型,所操控的数据类型被指定为一个参数。泛型不存在于JVM虚拟机。泛型学习、泛型限定类型、泛型通配符、泛型继承。泛型擦除带来的影响,泛型具体如何擦除。

    30编程范式游记(3)- 类型系统和泛型的本质1

    是的,除了C++那样的泛型,了解其它编程语言你一定会发现,在动态类型语言或是某些有语法糖支持的语言中,那个swap() 或 search() 函数的泛型其实可以

    java反射之通过反射了解集合泛型的本质(详解)

    下面小编就为大家带来一篇java反射之通过反射了解集合泛型的本质(详解)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧

    java接口泛型

    泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。

    尚硅谷-实验1:集合中使用泛型.pdf

    ·拒绝晦涩难懂的呆板教学,宋老师语言生动幽默,举例形象生动深入浅出,迅速让你把握问题本质,四两拨千斤 2.课程内容推陈出新: ·基于JDK 11,将Java8、Java9、Java10、Java11新特性一网打尽 ·课程中,Eclipse...

    尚硅谷-实验2:自定义泛型类的使用.pdf

    ·拒绝晦涩难懂的呆板教学,宋老师语言生动幽默,举例形象生动深入浅出,迅速让你把握问题本质,四两拨千斤 2.课程内容推陈出新: ·基于JDK 11,将Java8、Java9、Java10、Java11新特性一网打尽 ·课程中,Eclipse...

    简单理解java泛型的本质(非类型擦除)

    泛型在java中有很重要的地位,在面向对象编程及各种设计模式中有非常广泛的应用。泛型是参数化类型的应用,操作的数据类型不限定于特定类型,可以根据实际需要设置不同的数据类型,以实现代码复用。下面小编来简单讲...

    C#基础之泛型

    1.泛型的本质  泛型的好处不用多说,在.NET中我看到有很多技术都是以泛型为基础的,不过因为不懂泛型而只能对那些技术一脸茫然。泛型主要用于集合类,最主要的原因是它不需要装箱拆箱且类型安全,比如很常用的List...

    Apla→Java程序生成系统中泛型机制实现方法研究.pdf

    在对泛型程设本质特征深入研究的基础上提出了新型泛型语言机制构想,并在Apla→java生成系统中具体实现的新方法.该方法比现有的java、C++、C#等语言中泛型机制的实现方法简单,并通过经典算法实例演示实现效果,大量...

    JavaNotes:自学,存放学习笔记

    泛型本质 反射 多线程 文件类和IO流 Java8新特性 JDBC JavaSE易错点 JavaAdvance 活动 RPC 分散ID API网关 杰克逊 MyBatisPlus 正则表达式 重新组织函数 JavaWeb HTML,CSS JavaScript jQuery查询 Servelet ...

    Java8新特性之泛型的目标类型推断_动力节点Java学院整理

    泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。下面通过本文给分享Java8新特性之泛型的目标类型推断,感兴趣的朋友参考下吧

Global site tag (gtag.js) - Google Analytics