Javagenerics – types擦除 – 何时发生
我在Sun的网站上阅读了Java的types删除。
types擦除何时发生? 在编译时/运行时? 当类加载/运行时? 当类被实例化?
很多站点(包括上面提到的Sun教程)都说在编译时会发生types擦除。 如果types信息在编译时被彻底删除,那么在调用没有types信息或错误types信息的情况下,调用使用generics的方法时,JDK检查types兼容性如何。
考虑下面的例子:说A类有一个方法,为empty(Box<? extends Number> b)
。 我们编译A.java并获取类文件A.class。
现在我们创build另一个B类,它使用非参数化参数(原始types) – empty(new Box())
调用空方法。 如果我们在类path中使用A.class编译B.java,那么javac足够聪明,可以引发一个警告。 所以A.class 中存储了一些types信息。
我的猜测是,类加载时会发生types删除,但这只是一个猜测。 那么这是什么时候发生的?
types删除适用于generics的使用 。 在类文件中有绝对的元数据来表示一个方法/types是否是generics的,以及约束是什么等等。但是当generics被使用时 ,它们被转换成编译时间检查和执行时间转换。 所以这个代码:
List<String> list = new ArrayList<String>(); list.add("Hi"); String x = list.get(0);
被编译进去
List list = new ArrayList(); list.add("Hi"); String x = (String) list.get(0);
在执行的时候,没有办法找出列表对象的T=String
– 那个信息不见了。
…但是List<T>
接口本身仍然是通用的。
编辑:只是为了澄清,编译器确实保留有关该variables的信息是一个List<String>
– 但你仍然不能找出列表对象本身的T=String
。
编译器负责在编译时理解generics。 编译器也负责抛弃generics类的这种“理解”,在我们称之为types擦除的过程中。 全部发生在编译时。
注意:与大多数Java开发人员的看法相反,可以在运行时保留编译时types信息并检索这些信息,尽pipe使用非常有限。 换句话说, Java确实以非常有限的方式提供了泛化的generics 。
关于types删除
请注意,在编译时,编译器提供了完整的types信息,但是在生成字节码时,通常会故意删除该信息,称为types擦除 。 由于兼容性问题,这样做是这样做的:语言devise者的意图是提供完整的源代码兼容性以及平台版本之间的全字节代码兼容性。 如果实现方式不同,则在迁移到较新版本的平台时,必须重新编译旧应用程序。 这样做,所有的方法签名被保留(源代码兼容性),你不需要重新编译任何东西(二进制兼容性)。
关于Java中的泛化generics
如果您需要保留编译时types信息,则需要使用匿名类。 重点是:在匿名类的特殊情况下,可以在运行时检索完整的编译时types信息,换句话说就是:泛化generics。 这意味着编译器在涉及匿名类时不会丢弃types信息; 这些信息保存在生成的二进制代码中,运行时系统允许您检索这些信息。
我写了一篇关于这个主题的文章:
http://rgomes-info.blogspot.co.uk/2013/12/using-typetokens-to-retrieve-generic.html
关于在上面的文章中描述的技术的一个注意事项是,大多数开发人员的技术是模糊的。 尽pipe它的工作和运作良好,大多数开发人员感到困惑或不舒服的技术。 如果您有共享代码库或计划向公众发布您的代码,我不推荐上述技术。 另一方面,如果您是代码的唯一用户,则可以利用此技术为您提供的强大function。
示例代码
上面的文章链接到示例代码。
如果您有一个通用types的字段,则其types参数将被编译到该类中。
如果你有一个方法需要或返回一个genericstypes,这些types参数被编译到类中。
这个信息是编译器用来告诉你不能将Box<String>
传递给empty(Box<T extends Number>)
方法。
这个API很复杂,但是你可以通过getGenericParameterTypes
, getGenericReturnType
和getGenericReturnType
等方法通过reflectionAPI检查这个types的信息。
如果您有使用genericstypes的代码,则编译器根据需要(在调用者中)插入强制types来检查types。 通用对象本身只是原始types; 参数化types被“擦除”。 所以,当你创build一个new Box<Integer>()
, Box
对象中没有关于Integer
类的信息。
Angelika Langer的常见问题解答是我见过的Javagenerics最好的参考。
Java语言中的generics语言在这个话题上是非常好的指导。
generics是由Java编译器实现的,称为擦除的前端转换。 您可以(几乎)将其视为源到源的翻译,即通用版本的
loophole()
被转换为非通用版本。
所以,这是在编译时间。 JVM永远不会知道你使用了哪个ArrayList
。
我也推荐Skeet先生的答案什么是在Javagenerics擦除的概念?
types擦除发生在编译时。 什么types的擦除意味着它会忘记generics,而不是每种types。 此外,还会有关于通用types的元数据。 例如
Box<String> b = new Box<String>(); String x = b.getDefault();
被转换成
Box b = new Box(); String x = (String) b.getDefault();
在编译时。 你可能得到的警告不是因为编译器知道什么types是generics的,而是因为它不知道足够多,所以不能保证types安全。
此外,编译器确实保留了方法调用中有关参数的types信息,您可以通过reflection来检索这些信息。
本指南是我在这个主题上find的最好的。
我遇到types擦除在Android中。 在生产中我们使用gradle和minify选项。 小型化后,我有致命的例外。 我做了简单的函数来显示我的对象的inheritance链:
public static void printSuperclasses(Class clazz) { Type superClass = clazz.getGenericSuperclass(); Log.d("Reflection", "this class: " + (clazz == null ? "null" : clazz.getName())); Log.d("Reflection", "superClass: " + (superClass == null ? "null" : superClass.toString())); while (superClass != null && clazz != null) { clazz = clazz.getSuperclass(); superClass = clazz.getGenericSuperclass(); Log.d("Reflection", "this class: " + (clazz == null ? "null" : clazz.getName())); Log.d("Reflection", "superClass: " + (superClass == null ? "null" : superClass.toString())); } }
这个函数有两个结果:
未缩小的代码:
D/Reflection: this class: com.example.App.UsersList D/Reflection: superClass: com.example.App.SortedListWrapper<com.example.App.Models.User> D/Reflection: this class: com.example.App.SortedListWrapper D/Reflection: superClass: android.support.v7.util.SortedList$Callback<T> D/Reflection: this class: android.support.v7.util.SortedList$Callback D/Reflection: superClass: class java.lang.Object D/Reflection: this class: java.lang.Object D/Reflection: superClass: null
缩小的代码:
D/Reflection: this class: com.example.App.UsersList D/Reflection: superClass: class com.example.App.SortedListWrapper D/Reflection: this class: com.example.App.SortedListWrapper D/Reflection: superClass: class android.support.v7.ge D/Reflection: this class: android.support.v7.ge D/Reflection: superClass: class java.lang.Object D/Reflection: this class: java.lang.Object D/Reflection: superClass: null
因此,在缩小的代码中,实际的参数化类被replace为没有任何types信息的原始类types。 作为我的项目的解决scheme,我删除了所有的reflection调用,并用函数parameter passing的显式参数types来声明它们。