我使用javap反编译了Map类。类定义仍然显示了泛型类型K和V的存在。这应该已经被类型擦除的概念所删除。为什么不会发生这种情况?
./javap -verbose java.util.Map
Classfile jar:file:/opt/jdk1.8.0_101/jre/lib/rt.jar!/java/util/Map.class
Last modified 22 Jun, 2016; size 4127 bytes
MD5 checksum 238f89b3e2ff9bebe07aa22b0a3493a9
Compiled from "Map.java"
public interface java.util.Map<K extends java.lang.Object, V extends java.lang.Object>
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
Constant pool:
如果完全删除了通用签名信息,除非您还拥有源代码,否则将无法使用泛型类型或方法。想一想:为了有效地使用泛型,编译器必须知道类型或方法是通用的,并且它必须知道泛型参数的数量,位置和边界。
为此,javac
在类型和方法上发出所谓的Signature
属性,这些属性和方法本身是通用的,或者其签名包含类型变量或其他泛型类型的实例化。
对于像Map<K, V>
这样的泛型类型,类定义将使用Signature
属性发出,描述:
对于Map
接口,Signature
值如下所示:
<K:Ljava/lang/Object;V:Ljava/lang/Object;>Ljava/lang/Object;
您可以在输出的最后一个javap -v
中查看此属性,在关闭}
之后的行上。要查看更完整的通用签名是什么样的,请查看HashMap
类,它具有通用基类并实现多个接口:
<K:Ljava/lang/Object;V:Ljava/lang/Object;>Ljava/util/AbstractMap<TK;TV;>;Ljava/util/Map<TK;TV;>;Ljava/lang/Cloneable;Ljava/io/Serializable
从这个签名,编译器知道关于类型HashMap
的以下内容:
K
和V
,两者都扩展了java.lang.Object
。java.util.AbstractMap<K, V>
。为了澄清,K
和V
在这里指的是HashMap
(不是AbstractMap
)定义的参数。java.util.Map<K, V>
,java.lang.Cloneable
和java.io.Serializable
。方法也可能具有Signature
属性,但在方法的情况下,签名描述:
但是,方法的Signature
被认为是额外的元数据;你永远不会在字节码中直接看到一个引用。相反,您将看到对方法描述符的引用,该方法描述符类似于递归应用了通用擦除的签名。与Signature
属性不同,方法描述符是强制性的。 javap -v
非常友好地向您展示。例如,给定HashMap
方法public V put(K, V)
:
(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
。Signature
是(TK;TV;)TV;
。Signature
告诉编译器和您的IDE该方法的完整通用签名,从而实现类型安全的强制执行。描述符是如何在调用站点的字节码中实际引用该方法。例如,给定表达式map.put(0, "zero")
,其中map
是Map<Integer, String
>,指令序列将类似于:
aload (some variable holding a Map)
iconst_0
invokestatic java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
ldc "zero"
invokeinterface java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
请注意如何保留通用信息。通过插入执行运行时强制转换的checkcast
指令,在运行时强制执行有限类型安全性。例如,在map.get(0)
上调用Map<Integer, String>
将包含类似于以下的指令序列:
aload (some variable holding a Map)
iconst_0
invokestatic java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
invokeinterface java/util/Map.get:(Ljava/lang/Object;)Ljava/lang/Object;
checkcast Ljava/lang/String;
因此,即使Map
类型在调用站点被完全擦除,发出的字节码也确保从Map<Integer, String>
检索的任何值实际上是String
,而不是其他一些Object
。
重要的是要强调,与类文件中的大多数元数据一样,Signature
属性是完全可选的。虽然javac
会在必要时发出它们,但它们可能被后处理器(如字节码优化器和混淆器)剥离。当然,这将使得不可能以预期的方式使用泛型。例如,如果你要删除Signature
中的java/util/Map.class
属性,你只能使用Map
作为一个等同于Map<Object, Object>
的非泛型类,你必须自己处理类型检查。
字节码内部有额外的信息用于解码通用信息。