Java 泛型、擦除和字节码
我是Java泛型新手,我一直很难弄清楚它的内部工作原理。如果编译器执行的擦除删除了.java文件中的所有类型参数,并生成了一个旧JVM可以理解的普通.class文件,那么我们如何能够从其他类中引用这样一个类,知道java编译器在引用程序中的其他类时使用的是.class文件吗?编译器如何处理该.class文件中的所有对象引用,以确定哪个是原始对象,哪个是擦除的结果?类和方法签名中的泛型以及成员变量不会被擦除 一个简单的类:Java 泛型、擦除和字节码,java,generics,Java,Generics,我是Java泛型新手,我一直很难弄清楚它的内部工作原理。如果编译器执行的擦除删除了.java文件中的所有类型参数,并生成了一个旧JVM可以理解的普通.class文件,那么我们如何能够从其他类中引用这样一个类,知道java编译器在引用程序中的其他类时使用的是.class文件吗?编译器如何处理该.class文件中的所有对象引用,以确定哪个是原始对象,哪个是擦除的结果?类和方法签名中的泛型以及成员变量不会被擦除 一个简单的类: class Foo<T> { T field; vo
class Foo<T> {
T field;
void bar(List<T> list) {
T obj = list.get(0);
T zip = field;
}
}
class-Foo{
T场;
空栏(列表){
T obj=list.get(0);
T zip=字段;
}
}
反编译:
class Foo<T> { // Still got the <T> here.
T field; // Still got the T here.
Foo();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
void bar(java.util.List<T>); // Still got the <T> here.
Code:
0: aload_1
1: iconst_0
// But T has been erased inside the method body.
2: invokeinterface #2, 2 // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
7: astore_2
8: aload_0
// And T has been erased when referencing field.
9: getfield #3 // Field field:Ljava/lang/Object;
12: astore_3
13: return
}
class Foo{//在这里仍然有。
T field;//这里仍然有T。
Foo();
代码:
0:aload_0
1:invokespecial#1//方法java/lang/Object。“:()V
4:返回
void bar(java.util.List);//这里仍然有。
代码:
0:aload_1
1:iconst_0
//但是T在方法体中被擦除了。
2:invokeinterface#2,2//InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
7:astore_2
8:aload_0
//在引用字段时,T已被擦除。
9:getfield#3//Field:Ljava/lang/Object;
12:astore_3
13:返回
}
生成旧JVM可以理解的普通.class文件 事实并非如此:如果编译使用泛型的代码,则不支持泛型的JVM无法理解它
在早期版本上编译的类文件与以后的JVM兼容,但不是相反。简而言之,有关泛型及其在类型声明、方法签名等中的约束的详细信息仍然在字节码中编码为元数据 编译器在编译时使用该信息,但JVM在运行时不使用它。这些信息可以通过反射来访问,有些库使用它(Hibernate就是这样做的) 看更多 编辑:一个小实验,看它在实践中发挥作用。 作为对@Andy Turner答案的补充(这是非常有用的:它显示了泛型类型信息),让我们看看在运行时会发生什么 我们通过反射检查类结构,并用
字符串代替整数构建Foo
:
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
class Foo<T> {
T field;
void bar(List<T> list) {
T obj = list.get(0);
T zip = field;
}
public static void main(String[] args) throws ReflectiveOperationException {
Field field = Foo.class.getDeclaredField("field");
System.out.println("Field:"
+ "\n - " + field.getType()
+ "\n - " + field.getGenericType()
+ "\n - " + field.getAnnotatedType()
);
Method method = Foo.class.getDeclaredMethod("bar", List.class);
System.out.println("Method:"
+ "\n - " + Arrays.toString(method.getParameterTypes())
+ "\n - " + Arrays.toString(method.getGenericParameterTypes())
);
Foo<Integer> foo = new Foo<>();
// foo.field = "hi"; <- Compile error, incompatible types
field.set(foo, "hi"); //
// Integer value = foo.field; <- Accepted by compiler, fails at runtime with ClassCastException
Object value = foo.field; // OK
System.out.println("Value of field: " + value + " (class: " + value.getClass() + ")");
}
}
import java.lang.reflect.Field;
导入java.lang.reflect.Method;
导入java.util.array;
导入java.util.List;
福班{
T场;
空栏(列表){
T obj=list.get(0);
T zip=字段;
}
公共静态void main(字符串[]args)抛出ReflectiveOperationException{
字段字段=Foo.class.getDeclaredField(“字段”);
System.out.println(“字段:”
+“\n-”+字段。getType()
+“\n-”+字段。getGenericType()
+“\n-”+字段。getAnnotatedType()
);
Method=Foo.class.getDeclaredMethod(“bar”,List.class);
System.out.println(“方法:”
+“\n-”+Arrays.toString(method.getParameterTypes())
+“\n-”+Arrays.toString(method.getGenericParameterTypes())
);
Foo-Foo=新的Foo();
//foo.field=“hi”谢谢。但这对我来说是新鲜事:我认为不管怎样,生成的字节码都是一样的,擦除只是帮助保持了这一点。这是源代码的事情-编译器领域..,但不太确定。
Field:
- class java.lang.Object
- T
- sun.reflect.annotation.AnnotatedTypeFactory$AnnotatedTypeVariableImpl@5a2e4553
Method:
- [interface java.util.List]
- [java.util.List<T>]
Value of field: hi (class: class java.lang.String)