java泛型类型擦除

声明类型参数为 <T extends A & B & C> 的类时,类型擦除过程将使用类型 A 替换 T . 但是如果 T 类型的变量调用在接口 BC 中声明的方法,Java会用它做什么?

当我们创建一个泛型类型的实例,如 ArrayList<String> 时,类型参数 String 也将被删除,但调用 get 方法将返回类型 String ,这些信息来自哪里,因为它已被删除?

我知道使用java反射,但我需要一些具体的解释 .

回答(2)

2 years ago

不使用反射 - 铸造是 . 例如,请使用以下代码:

interface A {
    void a();
}

interface B {
    void b();
}

interface C {
    void c();
}

class Generic<T extends A & B & C> {
    T t;

    Generic(T t) {
        this.t = t;
    }

    void callMethods() {
        t.a();
        t.b();
        t.c();
    }
}

现在看一下 Generic 的字节码(删除了构造函数):

class Generic extends java.lang.Object{
A t;

void callMethods();
  Code:
   0:   aload_0
   1:   getfield        #2; //Field t:LA;
   4:   invokeinterface #3,  1; //InterfaceMethod A.a:()V
   9:   aload_0
   10:  getfield        #2; //Field t:LA;
   13:  checkcast       #4; //class B
   16:  invokeinterface #5,  1; //InterfaceMethod B.b:()V
   21:  aload_0
   22:  getfield        #2; //Field t:LA;
   25:  checkcast       #6; //class C
   28:  invokeinterface #7,  1; //InterfaceMethod C.c:()V
   33:  return    
}

在每个invokeinterface调用 b()c() 之前,请注意checkcast指令 .

结果就像 Generic 实际上是这样编写的:

class Generic<T extends A> {
    T t;

    Generic(T t) {
        this.t = t;
    }

    void callMethods() {
        t.a();
        ((B) t).b();
        ((C) t).c();
    }
}

至于你关于 ArrayList 的问题 - 有关作为列表元素类型的 get() 的返回类型的信息仍然存储为 ArrayList 类的一部分 . 编译器将再次在调用代码中插入强制转换,因此:

ArrayList<String> strings = new ArrayList<String>();
strings.add("foo");
String x = strings.get(0);

在执行时相当于:

ArrayList strings = new ArrayList();
strings.add("foo");
String x = (String) strings.get(0);

这样做的一个重要方面是你不能在执行时询问 ArrayList 对象 T 是什么 - 该信息已被删除 .

2 years ago

在编译器完成类型检查后,类型擦除有效地用 Object 替换所有泛型类型参数 . 在您的 <T extends A & B & C> 示例中, A 就像其他所有内容一样被删除 .

当您在 ArrayList<String> 上调用 get 时,编译器会生成字节码,将返回的对象自动转换为字符串 . 列表本身并不是一个字符串列表(由于类型擦除),但是调用 get 的代码知道它希望从列表中获取的东西是一个字符串 .