我认为java擦除会在编译时擦除泛型类型,但是当我自己测试它时,我意识到在Bytecode中有一些关于泛型类型的信息 .
这是我的测试:
我写了2节课:
import java.util.*;
public class Test {
List integerList;
}
和
import java.util.*;
public class Test {
List<Integer> integerList;
}
我编译了这两个类和泛型类中的某个地方,我看到了这一行
integerList{blah blah}Ljava/util/List;{blah blah}
Signature{blah blah}%Ljava/util/List<Ljava/lang/Integer;>;{blah blah}<init>
在非泛型类中:
integerList{blah blah}Ljava/util/List;{blah blah}<init>
所以显然我在字节码里面有通用信息所以这个擦除的东西是什么?
5 回答
某些通用类型信息存储在
Signature
属性中 . 请参阅JLS 4.8和4.6和JVM spec 4.3.4 . 阅读here:和Neal Gafter's blog .
Erasure 是从泛型到原始类型的映射 . 常用短语"because of erasure"基本上没有意义 . 重要的是使用映射的规范 .
有两个有趣的用途 .
它's used to map method signatures from using generics to raw types. It is the raw-type signatures that used for overloading. This causes the vast majority of the problems with 2934161 . For instance, you can' t有两种方法
add(List<String>)
和add(List<Integer>)
在同一类型 . 重载可能不是很好的添加此功能的意愿 .运行时对象实例可用的类型是使用已擦除创建的类型 . 因此,如果转换为
(String)
,将在运行时检查,但如果转换为List<String>
,则仅检查该类型的擦除(List
) . 您可以将List<String>
和List<Integer>
类型的变量指向完全相同的实例 . 实际上,您不应该在1.5及更高版本中使用强制转换(引用类型) .在可行的情况下,通用信息保存在类文件中并通过反射提供 . 所以你会在类定义,超类型,字段,方法,构造函数等上找到它 .
这是准确使用术语实际上很重要的一个例子:字节码是Java虚拟机的指令集 . 类文件包含字节码,但也包含用于链接的信息(字段签名,方法签名,...),字节码验证器,调试器,...
类型擦除意味着泛型类型信息不会转换为字节代码;更具体地说,泛型类型的所有实例在字节代码中共享相同的表示 . 同样,运行时跟踪的对象的动态类型(由cast和instanceof运算符使用,并且可通过getClass()获得)对于泛型类的所有实例都是相同的,无论源中提供的任何类型参数如何码 .
您的实验证明泛型类型信息保留在类文件中,更具体地说,保留在方法类型和字段签名中 . 这并不奇怪,因为签名实际上是在编译时使用的 . 也可以在链接时使用,甚至可以通过反射api访问 . 关键的区别在于它们是声明的字段或方法类型,而不是实际对象的运行时类型 .
也就是说,从Java 1.5开始,我们必须区分变量的声明类型和它引用的对象的运行时类型 . 前者支持泛型,后者则不支持 . 是的,这意味着编译时和运行时类型之间没有一对一的对应关系 .
类型信息将从此处删除
在字节码中它将等同于
并且没有机会在运行时知道integerList对象的编译时间类型是什么 .
擦除意味着通用类型不包含在字节代码中(当创建或使用列表时) .
您看到的签名仅用于表示该字段是通用的 .