我在Oracle的网站上读到了Java的类型擦除.
什么时候发生类型擦除?在编译时还是运行时?当班级加载?当类被实例化时?
很多站点(包括上面提到的官方教程)都说在编译时会发生类型擦除.如果在编译时完全删除了类型信息,那么当调用使用泛型的方法而没有类型信息或错误的类型信息时,JDK如何检查类型兼容性?
考虑以下示例:Say class A
有一个方法,empty(Box extends Number> b)
.我们编译A.java
并获取类文件A.class
.
public class A { public static void empty(Box extends Number> b) {} }
public class Box{}
现在我们创建另一个类B
,该类empty
使用非参数化参数(原始类型)调用该方法:empty(new Box())
.如果我们编译B.java
与A.class
在类路径中,javac的是足够聪明,引发警告.所以A.class
有一些类型信息存储在其中.
public class B { public static void invoke() { // java: unchecked method invocation: // method empty in class A is applied to given types // required: Box extends java.lang.Number> // found: Box // java: unchecked conversion // required: Box extends java.lang.Number> // found: Box A.empty(new Box()); } }
我的猜测是类加载时会发生类型擦除,但这只是猜测.那么什么时候发生?
类型擦除适用于泛型的使用.人们确实在类文件元数据说的方法/类型是否是通用的,什么约束等,但仿制药的时候使用,他们转换成编译时检查和执行时间蒙上.所以这段代码:
Listlist = new ArrayList (); list.add("Hi"); String x = list.get(0);
编译成
List list = new ArrayList(); list.add("Hi"); String x = (String) list.get(0);
在执行时,无法找到T=String
列表对象 - 信息消失了.
...但是List
界面本身仍然宣称自己是通用的.
编辑:只是为了澄清,编译器并保留有关的信息变量是一个List
-但您仍然无法找出T=String
列表对象本身.
编译器负责在编译时理解泛型.编译器还负责在我们称为类型擦除的过程中抛弃对泛型类的这种"理解" .一切都发生在编译时.
注意:与大多数Java开发人员的信念相反,尽管采用非常有限的方式,仍可以保留编译时类型信息并在运行时检索此信息.换句话说:Java确实以非常有限的方式提供了具体化的泛型.
请注意,在编译时,编译器具有可用的完整类型信息,但在生成字节代码时,通常会在称为类型擦除的过程中有意删除此信息.由于兼容性问题,这是通过这种方式完成的:语言设计者的意图是提供完整的源代码兼容性和平台版本之间的完全字节代码兼容性.如果以不同方式实现,则在迁移到较新版本的平台时,您必须重新编译旧版应用程序.完成它的方式,保留所有方法签名(源代码兼容性),您不需要重新编译任何东西(二进制兼容性).
如果需要保留编译时类型信息,则需要使用匿名类.关键是:在匿名类的非常特殊的情况下,可以在运行时检索完整的编译时类型信息,换言之,意思是:具体化的泛型.这意味着当涉及匿名类时,编译器不会丢弃类型信息; 此信息保存在生成的二进制代码中,运行时系统允许您检索此信息.
我写了一篇关于这个主题的文章:
http://rgomes-info.blogspot.co.uk/2013/12/using-typetokens-to-retrieve-generic.html
关于上述文章中描述的技术的注释是,对于大多数开发人员而言,该技术是模糊的.尽管它工作正常并且运行良好,但大多数开发人员对此技术感到困惑或不安.如果您有共享代码库或计划向公众发布代码,我不建议使用上述技术.另一方面,如果您是代码的唯一用户,则可以利用此技术为您提供的强大功能.
上面的文章包含示例代码的链接.
如果您有一个泛型类型的字段,则其类型参数将编译到该类中.
如果您有一个接受或返回泛型类型的方法,那么这些类型参数将被编译到类中.
此信息是编译器用于告诉您无法将a传递Box
给empty(Box
方法的信息.
该API是复杂的,但你可以通过检查用类似方法反射API这种类型的信息getGenericParameterTypes
,getGenericReturnType
以及,对田,getGenericType
.
如果您有使用泛型类型的代码,则编译器会根据需要(在调用方中)插入强制转换来检查类型.通用对象本身只是原始类型; 参数化类型被"擦除".因此,在创建a时new Box
,没有关于对象中的Integer
类的信息Box
.
Angelika Langer的FAQ是我在Java Generics中看到的最佳参考.
Java语言中的泛型是一个非常好的指南.
泛型由Java编译器实现为称为擦除的前端转换.您可以(几乎)将其视为源到源的转换,从而将通用版本
loophole()
转换为非泛型版本.
所以,它是在编译时.JVM永远不会知道ArrayList
您使用了哪个.
我还建议Skeet先生的答案是什么是Java中泛型的擦除概念?
类型擦除发生在编译时.什么类型的擦除意味着它会忘记泛型类型,而不是每种类型.此外,仍然会有关于通用类型的元数据.例如
Boxb = new Box (); String x = b.getDefault();
转换为
Box b = new Box(); String x = (String) b.getDefault();
在编译时.您可能会收到警告,不是因为编译器知道通用类型是什么类型,而是相反,因为它不够了解所以它不能保证类型安全.
此外,编译器会在方法调用中保留有关参数的类型信息,您可以通过反射检索这些信息.
本指南是我在这个主题上发现的最好的.
术语“类型擦除”实际上并不是对Java泛型问题的正确描述。类型擦除本身并不是一件坏事,实际上对于性能而言这是非常必要的,并且经常用于多种语言,例如C ++,Haskell,D。
厌恶之前,请记得从Wiki正确定义类型擦除
什么是类型擦除?
类型擦除是指在运行时执行之前从程序中删除显式类型注释的加载时过程。
类型擦除是指丢弃在设计时创建的类型标签或在编译时推断的类型标签,以使二进制代码中的已编译程序不包含任何类型标签。对于每种编译为二进制代码的编程语言来说都是这种情况,除非在某些情况下需要运行时标签。这些例外包括所有存在类型(可引用的Java参考类型,多种语言中的任何类型,联合类型)。删除类型的原因是程序被转换为某种类型的单类型语言(二进制语言仅允许位),因为类型仅是抽象,并断言其值的结构和适当的语义来处理它们。
因此,这是正常的自然回报。
Java的问题有所不同,并导致其如何统一。
关于Java的常见陈述没有泛型泛型也是错误的。
Java确实进行了验证,但是由于向后兼容而以错误的方式进行了验证。
什么是具体化?
从我们的维基
验证是将有关计算机程序的抽象概念转换为显式数据模型或以编程语言创建的其他对象的过程。
归一化是指通过专门化将抽象的东西(参数类型)转换为具体的东西(混凝土类型)。
我们通过一个简单的例子来说明这一点:
具有定义的ArrayList:
ArrayList{ T[] elems; ...//methods }
是一个抽象,详细地讲是一个类型构造函数,当专门用于具体类型时,它会被“重新定义”,例如整数:
ArrayList{ Integer[] elems; }
哪里ArrayList
真的是类型。
但这正是 Java所没有的!!!,而是使用边界来不断地抽象化抽象类型,即产生相同的具体类型,而与为进行专门化而传入的参数无关:
ArrayList { Object[] elems; }
此处使用隐式绑定Object(ArrayList
== ArrayList
)进行了修正。
尽管如此,它使泛型数组不可用,并且对原始类型造成一些奇怪的错误:
Listl= List. of("h","s"); List lRaw=l l.add(new Object()) String s=l.get(2) //Cast Exception
它引起很多歧义,因为
void function(ArrayListlist){} void function(ArrayList list){} void function(ArrayList list){}
指相同的功能:
void function(ArrayList list)
因此,常规方法重载不能在Java中使用。