Java泛型类型擦除:何时发生以及发生什么?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/339699/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Java generics type erasure: when and what happens?
提问by
I read about Java's type erasure on Oracle's website.
我在 Oracle 的网站上阅读了 Java 的类型擦除。
When does type erasure occur?At compile time or runtime? When the class is loaded? When the class is instantiated?
何时发生类型擦除?在编译时还是运行时?类何时加载?类什么时候实例化?
A lot of sites (including the official tutorial mentioned above) say type erasure occurs at compile time. If the type information is completely removed at compile time, how does the JDK check type compatibility when a method using generics is invoked with no type information or wrong type information?
很多站点(包括上面提到的官方教程)都说类型擦除发生在编译时。如果在编译时完全去除了类型信息,那么当调用使用泛型的方法没有类型信息或错误类型信息时,JDK如何检查类型兼容性?
Consider the following example: Say class A
has a method, empty(Box<? extends Number> b)
. We compile A.java
and get the class file A.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<T> {}
Now we create another class B
which invokes the method empty
with a non-parameterized argument (raw type): empty(new Box())
. If we compile B.java
with A.class
in the classpath, javac is smart enough to raise a warning. So A.class
hassome type information stored in it.
现在,我们创建另一个类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());
}
}
My guess would be that type erasure occurs when the class is loaded, but it is just a guess. So when does it happen?
我的猜测是在加载类时会发生类型擦除,但这只是一个猜测。那么什么时候发生呢?
回答by Eugene Yokota
Generics in Java Languageis a really good guide on this topic.
Generics are implemented by Java compiler as a front-end conversion called erasure. You can (almost) think of it as a source-to-source translation, whereby the generic version of
loophole()
is converted to the non-generic version.
泛型由 Java 编译器作为前端转换实现,称为擦除。您可以(几乎)将其视为源到源的翻译,从而将 的通用版本
loophole()
转换为非通用版本。
So, it's at compile time. The JVM will never know which ArrayList
you used.
所以,它是在编译时。JVM 永远不会知道ArrayList
您使用的是哪个。
I'd also recommend Mr. Skeet's answer on What is the concept of erasure in generics in Java?
我还推荐 Skeet 先生关于Java 中泛型擦除的概念是什么?
回答by Jon Skeet
Type erasure applies to the useof generics. There's definitely metadata in the class file to say whether or not a method/type isgeneric, and what the constraints are etc. But when generics are used, they're converted into compile-time checks and execution-time casts. So this code:
类型擦除适用于泛型的使用。有一个在类文件的元数据肯定说的方法/类型是否是通用的,什么约束等,但仿制药的时候使用,他们转换成编译时检查和执行时间转换。所以这段代码:
List<String> list = new ArrayList<String>();
list.add("Hi");
String x = list.get(0);
is compiled into
被编译成
List list = new ArrayList();
list.add("Hi");
String x = (String) list.get(0);
At execution time there's no way of finding out that T=String
for the list object - that information is gone.
在执行时,无法找到T=String
列表对象的信息 - 该信息已经消失。
... but the List<T>
interface itself still advertises itself as being generic.
...但List<T>
接口本身仍然宣称自己是通用的。
EDIT: Just to clarify, the compiler does retain the information about the variablebeing a List<String>
- but you still can't find out that T=String
for the list object itself.
编辑:只是为了澄清,编译器确实保留了有关变量为 a的信息List<String>
- 但您仍然无法找到T=String
列表对象本身的信息。
回答by Vinko Vrsalovic
Type erasure occurs at compile time. What type erasure means is that it will forget about the generic type, not about every type. Besides, there will still be metadata about the types being generic. For example
类型擦除发生在编译时。类型擦除意味着它会忘记泛型类型,而不是所有类型。此外,仍然会有关于泛型类型的元数据。例如
Box<String> b = new Box<String>();
String x = b.getDefault();
is converted to
转换为
Box b = new Box();
String x = (String) b.getDefault();
at compile time. You may get warnings not because the compiler knows about what type is the generic of, but on the contrary, because it doesn't know enough so it cannot guarantee type safety.
在编译时。您可能会收到警告,不是因为编译器知道泛型是什么类型,而是因为它知道的不够多,因此无法保证类型安全。
Additionally, the compiler does retain the type information about the parameters on a method call, which you can retrieve via reflection.
此外,编译器确实保留了有关方法调用参数的类型信息,您可以通过反射检索这些信息。
This guideis the best I've found on the subject.
回答by erickson
If you have a field that is a generic type, its type parameters are compiled into the class.
如果您有一个泛型类型的字段,则其类型参数将被编译到类中。
If you have a method that takes or returns a generic type, those type parameters are compiled into the class.
如果您有一个接受或返回泛型类型的方法,这些类型参数将被编译到类中。
This information is what the compiler uses to tell you that you can't pass a Box<String>
to the empty(Box<T extends Number>)
method.
此信息是编译器用来告诉您不能将 a 传递Box<String>
给empty(Box<T extends Number>)
方法的信息。
The API is complicated, but you can inspect this type information through the reflection API with methods like getGenericParameterTypes
, getGenericReturnType
, and, for fields, getGenericType
.
该API是复杂的,但你可以通过检查用类似方法反射API这种类型的信息getGenericParameterTypes
,getGenericReturnType
以及,对田,getGenericType
。
If you have code that uses a generic type, the compiler inserts casts as needed (in the caller) to check types. The generic objects themselves are just the raw type; the parameterized type is "erased". So, when you create a new Box<Integer>()
, there is no information about the Integer
class in the Box
object.
如果您有使用泛型类型的代码,编译器会根据需要(在调用者中)插入强制转换以检查类型。泛型对象本身只是原始类型;参数化类型是“擦除”。因此,当您创建 时new Box<Integer>()
,对象中没有关于Integer
该类的信息Box
。
Angelika Langer's FAQis the best reference I've seen for Java Generics.
Angelika Langer 的 FAQ是我见过的关于 Java 泛型的最佳参考。
回答by Richard Gomes
The compiler is responsible for understanding Generics at compile time. The compiler is also responsible for throwing away this "understanding" of generic classes, in a process we call type erasure. All happens at compile time.
编译器负责在编译时理解泛型。编译器还负责丢弃对泛型类的这种“理解”,在我们称为类型擦除的过程中。一切都发生在编译时。
Note:Contrary to beliefs of majority of Java developers, it is possible to keep compile-time type information and retrieve this information at runtime, despite in a very restricted way. In other words: Java does provide reified generics in a very restricted way.
注意:与大多数 Java 开发人员的看法相反,尽管以非常有限的方式保留编译时类型信息并在运行时检索此信息是可能的。换句话说:Java 确实以非常有限的方式提供了具体化的泛型。
Regarding type erasure
关于类型擦除
Notice that, at compile-time, the compiler has full type information available but this information is intentionally dropped in generalwhen the byte code is generated, in a process known as type erasure. This is done this way due to compatibility issues: The intention of language designers was providing full source code compatibility and full byte code compatibility between versions of the platform. If it were implemented differently, you would have to recompile your legacy applications when migrating to newer versions of the platform. The way it was done, all method signatures are preserved (source code compatibility) and you don't need to recompile anything (binary compatibility).
请注意,在编译时,编译器具有可用的完整类型信息,但通常在生成字节码时有意删除此信息,称为类型擦除。由于兼容性问题,这是这样做的:语言设计者的意图是在平台版本之间提供完整的源代码兼容性和完整的字节码兼容性。如果以不同方式实现,则在迁移到新版本的平台时必须重新编译旧应用程序。这样做的方式是保留所有方法签名(源代码兼容性),您不需要重新编译任何内容(二进制兼容性)。
Regarding reified generics in Java
关于 Java 中的具体泛型
If you need to keep compile-time type information, you need to employ anonymous classes. The point is: in the very special case of anonymous classes, it is possible to retrieve full compile-time type information at runtime which, in other words means: reified generics. This means that the compiler does not throw away type information when anonymous classes are involved; this information is kept in the generated binary code and the runtime system allows you to retrieve this information.
如果需要保留编译时类型信息,则需要使用匿名类。关键是:在匿名类的非常特殊的情况下,可以在运行时检索完整的编译时类型信息,换句话说,这意味着:具体化泛型。这意味着当涉及匿名类时,编译器不会丢弃类型信息;此信息保存在生成的二进制代码中,运行时系统允许您检索此信息。
I've written an article about this subject:
我写了一篇关于这个主题的文章:
https://rgomes.info/using-typetokens-to-retrieve-generic-parameters/
https://rgomes.info/using-typetokens-to-retrieve-generic-parameters/
A note about the technique described in the article above is that the technique is obscure for majority of developers. Despite it works and works well, most developers feel confused or uncomfortable with the technique. If you have a shared code base or plan to release your code to the public, I do not recommend the above technique. On the other hand, if you are the sole user of your code, you can take advantage of the power this technique delivers to you.
关于上述文章中描述的技术的一个说明是,该技术对于大多数开发人员来说是模糊的。尽管它运行良好并且运行良好,但大多数开发人员对该技术感到困惑或不舒服。如果您有共享的代码库或计划向公众发布您的代码,我不推荐上述技术。另一方面,如果您是代码的唯一用户,则可以利用该技术为您提供的强大功能。
Sample code
示例代码
The article above has links to sample code.
上面的文章有示例代码的链接。
回答by porfirion
I've encountered with type erasure in Android. In production we use gradle with minify option. After minification I've got fatal exception. I've made simple function to show inheritance chain of my object:
我在 Android 中遇到过类型擦除。在生产中,我们使用带有 minify 选项的 gradle。缩小后,我遇到了致命的异常。我做了一个简单的函数来显示我的对象的继承链:
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()));
}
}
And there is two results of this function:
这个函数有两个结果:
Not minified code:
未缩小代码:
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
Minified code:
缩小代码:
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.g.e
D/Reflection: this class: android.support.v7.g.e
D/Reflection: superClass: class java.lang.Object
D/Reflection: this class: java.lang.Object
D/Reflection: superClass: null
So, in minified code actual parametrized classes are replaced with raw classes types without any type information. As a solution for my project i removed all reflection calls and replced them with explicit params types passed in function arguments.
因此,在缩小代码中,实际参数化类被替换为没有任何类型信息的原始类类型。作为我的项目的解决方案,我删除了所有反射调用,并使用在函数参数中传递的显式参数类型重新调用它们。
回答by iconfly
The term "type erasure" is not really the correct description of Java's problem with generics. Type erasure is not per se a bad thing, indeed it is very necessary for performance and is often used in several languages like C++, Haskell, D.
术语“类型擦除”并不是对 Java 泛型问题的正确描述。类型擦除本身并不是一件坏事,实际上它对于性能来说是非常必要的,并且经常在 C++、Haskell、D 等几种语言中使用。
Before you disgust, please recall the correct definition of type erasure from Wiki
在你反感之前,请回忆一下Wiki中类型擦除的正确定义
What is type erasure?
什么是类型擦除?
type erasure refers to the load-time process by which explicit type annotations are removed from a program, before it is executed at run-time
类型擦除是指加载时过程,在程序运行时执行之前,通过该过程从程序中删除显式类型注释
Type erasure means to throw away type tags created at design time or inferred type tags at compile time such that the compiled program in binary code does not contain any type tags. And this is the case for every programming language compiling to binary code except in some cases where you need runtime tags. These exceptions include for instance all existential types (Java Reference Types which are subtypeable, Any Type in many languages, Union Types). The reason for type erasure is that programs get transformed to a language which is in some kind uni-typed (binary language only allowing bits) as types are abstractions only and assert a structure for its values and the appropriate semantics to handle them.
类型擦除是指丢弃在设计时创建的类型标签或在编译时推断的类型标签,使得二进制代码编译的程序不包含任何类型标签。这对于编译为二进制代码的每种编程语言都是如此,除非在某些需要运行时标记的情况下。例如,这些例外包括所有存在类型(可子类型化的 Java 引用类型、许多语言中的任何类型、联合类型)。类型擦除的原因是程序被转换为某种单一类型的语言(二进制语言只允许位),因为类型只是抽象,并为其值和适当的语义断言来处理它们。
So this is in return, a normal natural thing.
所以这是作为回报,很自然的事情。
Java's problem is different and caused to how it reifies.
Java 的问题是不同的,并导致它如何具体化。
The often made statements about Java does not have reified generics is also wrong.
经常发表的关于 Java 没有具体化泛型的声明也是错误的。
Java does reify, but in a wrong way due to backward compatibility.
Java 确实 reify,但由于向后兼容性而以错误的方式。
What is reification?
什么是物化?
From our Wiki
来自我们的维基
Reification is the process by which an abstract idea about a computer program is turned into an explicit data model or other object created in a programming language.
具体化是将计算机程序的抽象概念转化为显式数据模型或用编程语言创建的其他对象的过程。
Reification means to convert something abstract (Parametric Type) into something concrete (Concrete Type) by specialization.
具体化是指通过专业化将抽象的东西(参数类型)转化为具体的东西(具体类型)。
We illustrate this by a simple example:
我们通过一个简单的例子来说明这一点:
An ArrayList with definition:
具有定义的 ArrayList:
ArrayList<T>
{
T[] elems;
...//methods
}
is an abstraction, in detail a type constructor, which gets "reified" when specialized with a concrete type, say Integer:
是一个抽象,详细来说是一个类型构造函数,当专门用于一个具体类型时,它会被“具体化”,比如 Integer:
ArrayList<Integer>
{
Integer[] elems;
}
where ArrayList<Integer>
is really a type.
whereArrayList<Integer>
真的是一个类型。
But this is exactlythe thing what Java does not!!!, instead they reify constantly abstract types with their bounds, i.e. producing the same concrete type independent of the parameters passed in for specialization:
但这正是Java没有的东西!!!,取而代之的是,它们不断地用它们的边界来具体化抽象类型,即产生相同的具体类型,独立于传入的参数进行专门化:
ArrayList
{
Object[] elems;
}
which is here reified with the implicit bound Object (ArrayList<T extends Object>
== ArrayList<T>
).
这是在这里用隐式绑定对象(ArrayList<T extends Object>
== ArrayList<T>
)具体化的。
Despite that it makes generic arrays unusable and cause some strange errors for raw types:
尽管如此,它使泛型数组无法使用并导致原始类型出现一些奇怪的错误:
List<String> l= List.<String>of("h","s");
List lRaw=l
l.add(new Object())
String s=l.get(2) //Cast Exception
it causes a lot of ambiguities as
它引起了很多歧义,因为
void function(ArrayList<Integer> list){}
void function(ArrayList<Float> list){}
void function(ArrayList<String> list){}
refer to the same function:
参考同一个函数:
void function(ArrayList list)
and therefore generic method overloading can't be used in Java.
因此不能在 Java 中使用泛型方法重载。