我在甲骨文的网站上读到Java的类型擦除。

什么时候发生类型擦除?在编译时还是运行时?什么时候加载类?类何时实例化?

很多网站(包括上面提到的官方教程)都说类型擦除发生在编译时。如果在编译时完全删除了类型信息,当调用使用泛型的方法时没有类型信息或类型信息错误时,JDK如何检查类型兼容性?

考虑下面的例子:假设类A有一个方法,空(Box<?我们编译A.java并获得类文件A.class。

public class A {
    public static void empty(Box<? extends Number> b) {}
}
public class Box<T> {}

现在我们创建另一个类B,它使用一个非参数化参数(原始类型)调用方法empty: empty(new Box())。如果在类路径中使用a .class编译B.java, 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());
    }
}

我的猜测是,类型擦除发生在类加载时,但这只是一个猜测。那么什么时候发生呢?


当前回答

Java语言中的泛型是关于这个主题的一个很好的指南。

泛型是由Java实现的 编译器作为前端转换 被称为擦除。你(几乎)会思考 来源对来源 翻译,由此泛化 漏洞()的版本被转换为 非通用版本。

它在编译时。JVM永远不会知道您使用了哪个数组列表。

我也推荐Skeet先生关于Java泛型中擦除的概念是什么?

其他回答

Java语言中的泛型是关于这个主题的一个很好的指南。

泛型是由Java实现的 编译器作为前端转换 被称为擦除。你(几乎)会思考 来源对来源 翻译,由此泛化 漏洞()的版本被转换为 非通用版本。

它在编译时。JVM永远不会知道您使用了哪个数组列表。

我也推荐Skeet先生关于Java泛型中擦除的概念是什么?

类型擦除适用于泛型的使用。类文件中肯定有元数据来说明方法/类型是否是泛型的,以及约束是什么等等。但是当使用泛型时,它们被转换为编译时检查和执行时强制转换。这段代码:

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>接口本身仍然宣称自己是通用的。

编辑:只是为了澄清,编译器确实保留了关于变量是List<String>的信息-但您仍然无法找到列表对象本身的T=String。

类型擦除发生在编译时。类型擦除意味着它将忘记泛型类型,而不是所有类型。此外,仍然会有关于泛型类型的元数据。例如

Box<String> b = new Box<String>();
String x = b.getDefault();

转换为

Box b = new Box();
String x = (String) b.getDefault();

在编译时。你可能会得到警告,不是因为编译器知道什么类型是的泛型,而是相反,因为它知道的不够多,所以它不能保证类型安全。

此外,编译器保留方法调用参数的类型信息,您可以通过反射检索这些信息。

这本指南是我在这方面找到的最好的。

如果字段是泛型类型,则其类型参数将编译到类中。

如果您有一个接受或返回泛型类型的方法,这些类型参数将被编译到类中。

这个信息是编译器用来告诉你你不能传递一个Box<String>给空方法(Box<T extends Number>)。

API很复杂,但是您可以通过反射API使用getGenericParameterTypes、getGenericReturnType和getGenericType(对于字段)等方法检查此类型信息。

如果有使用泛型类型的代码,编译器会根据需要(在调用者中)插入类型转换来检查类型。泛型对象本身只是原始类型;参数化类型被“擦除”。因此,当您创建一个新的Box<Integer>()时,Box对象中没有关于Integer类的信息。

Angelika Langer的FAQ是我所见过的关于Java泛型的最好参考。

我在Android中遇到过的类型擦除。在生产中,我们使用gradle和minify选项。在缩小之后,我得到了致命的例外。我做了一个简单的函数来显示我的对象的继承链:

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.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

因此,在简化代码中,实际的参数化类被替换为没有任何类型信息的原始类类型。 作为我的项目的解决方案,我删除了所有的反射调用,并用函数参数传递的显式参数类型替换它们。