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( );
}
分享到:
相关推荐
泛型本质上是一种参数化类型,它允许我们在编写代码时就指定容器(如集合)或方法所操作的数据类型。通过泛型,我们可以确保插入到集合中的元素都是同一类型的,避免了插入不兼容类型数据的可能性。例如,ArrayList...
- 泛型的本质是在类、接口或方法中使用类型参数,让它们能够处理多种数据类型。在Java中,泛型通常以尖括号 `<T>` 表示,其中 `T` 是类型参数,可以代表任何引用类型。 - 类如 `class Java_Generics,V>`,`K` 和 `...
泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。 Java语言引入泛型的好处是...
泛型本质上是参数化类型,允许我们在编写代码时,将数据类型作为参数,这使得类、接口和方法能够处理多种不同的数据类型。 在泛型方法中,我们可以定义一个通用的方法,它能够在编译时根据传入的参数类型自动进行...
泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。 Java语言引入泛型的好处是...
泛型本质上是一种参数化类型,它允许我们将类型作为一个参数传递给类、接口或方法。类型参数是这种参数化的一个关键概念,它在类、接口或方法中定义了一个位置,用于指定具体的类型。例如,在`List<T>`中,`T`就是...
在编译后,所有的类型参数都会被替换为它们的边界或者Object,这意味着在运行时,泛型对象与非泛型对象并无实质区别,但编译期的类型检查仍然有效。 5. **创建泛型实例**:创建泛型类的实例时,需要提供具体的类型...
泛型的本质是类型擦除,这意味着在编译后的字节码中,所有的类型参数都会被替换为它们的边界类型(通常是`Object`)。然而,编译器会插入强制类型转换和检查,以确保在运行时的类型安全。这就解释了为什么即使在类型...
本文将深入探讨C#中的泛型基础,包括泛型的本质、规范以及如何通过反射来创建泛型实例。 1. 泛型的本质 泛型的本质在于其类型参数化,允许开发者在定义类、接口或方法时声明一个或多个类型参数,如`T`。在实际使用...
窗体实质上是`System.Windows.Forms.Form`类的实例。 2. **设计界面**:在设计器中拖放控件并调整它们的布局和属性。每个控件都有自己的事件,比如按钮的Click事件。 3. **编写事件处理程序**:当用户与控件交互时...
例如,在Append函数中,函子本质上是配对。 #### 六、伴随与数据结构的关联 - **第2.1节**和**2.2节**提供了范畴论的背景知识,为后续内容做铺垫。 - **第2.3节**和**2.4节**展示了如何以范畴论的方式建模非递归数据...
1、Java泛型是J2 SE1.5中引入的一个新特性,其本质是参数化类型,也就是说所操作的数据类型被指定为一个参数(type parameter)这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。...
由于部分内容并未给出实质性的文本信息,只能从标题和描述出发进行展开讨论。 ### 泛型编程 #### 定义与特点 泛型编程是一种编程范式,它允许程序员编写可以处理多种数据类型的代码,而不是特定的一种或几种类型。...
理解它们的本质,有助于开发者编写出更优雅、更健壮的代码,同时也能更好地应对各种编程挑战。无论是静态类型还是动态类型的语言,都有其独特的优势和适用场景,选择合适的工具取决于具体的需求和项目特点。
对Java泛型的描述,何谓泛型呢?通俗的说,就是泛泛的指定对象所操作的类型,而不...泛型的本质就是将所操作的数据类型参数化,也就是说,该数据类型被指定为一个参数。这种参数类型可以使用在类、接口以及方法定义中。
泛型的本质是参数化类型,即在类、接口和方法中使用类型作为参数。本文将深入探讨Java泛型的工作原理、应用场景以及最佳实践。 Java泛型是提高代码重用性、类型安全性和减少类型转换错误的重要工具。了解泛型的原理...
泛型的本质是在类、接口或方法中使用类型参数,使得同一份代码可以处理多种数据类型。以Java中的`class Java_Generics, V>`为例,`K`和`V`就像方法参数一样,是类型参数,可以在实例化时替换为具体的类型,如`...
泛型的本质是参数化类型,允许我们在定义类、接口或方法时使用类型参数,而不是具体的类型。在实际使用时,我们可以根据需要传入具体的类型参数,使得代码更加灵活且具有针对性。 在泛型的基础部分,我们需要理解...