我使用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:
答案 0 :(得分:6)
如果通用签名信息被完全删除,则除非您也有源代码,否则将不可能使用通用类型或方法。考虑一下:为了有效地使用泛型,编译器必须知道一个类型或方法是泛型的,并且必须知道泛型参数的数量,位置和范围。
为此,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方法的完整泛型签名,从而可以强制执行类型安全。 descriptor 是在调用站点的字节码中实际引用该方法的方式。例如,给定表达式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>
检索到的所有值实际上都是 一个{{1 }},而不是其他String
。
需要强调的是,像类文件中的大多数元数据一样,Object
属性是完全可选的。尽管Signature
会在必要时发出它们,但它们有可能被字节码优化器和混淆器之类的后处理器剥离。当然,这将使得不可能以预期方式使用泛型。例如,如果要剥离javac
中的Signature
属性,则只能将java/util/Map.class
用作等效于Map
的非泛型类,必须自己检查类型。
答案 1 :(得分:0)
字节码中有额外的信息用于解码通用信息。