键入擦除在Java Map类中不起作用

时间:2017-09-27 10:27:21

标签: java generics type-erasure

我使用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:

2 个答案:

答案 0 :(得分:6)

如果通用签名信息被完全删除,则除非您也有源代码,否则将不可能使用通用类型或方法。考虑一下:为了有效地使用泛型,编译器必须知道一个类型或方法是泛型的,并且必须知道泛型参数的数量,位置和范围。

为此,javac在本身是通用的类型或方法上或者其签名包含类型变量或其他通用类型的实例的类型和方法上发出Signature属性。

对于诸如Map<K, V>之类的通用类型,将使用Signature属性来描述该类定义:

  1. 由类型声明的所有通用参数(类型变量)及其范围;
  2. 该类型的基类的完整通用签名;
  3. 由类型实现的接口的完整通用签名。

对于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的信息:

  1. 有两个通用参数KV,它们都扩展了java.lang.Object
  2. 基类为java.util.AbstractMap<K, V>。为了清楚起见,KV是指HashMap(不是AbstractMap)定义的参数。
  3. 该类实现java.util.Map<K, V>java.lang.Cloneablejava.io.Serializable

方法也可能具有Signature属性,但是在方法的情况下,签名描述:

  1. 该方法声明的所有通用参数(类型变量)及其范围;
  2. 该方法的参数类型的完整通用签名;
  3. 该方法的返回类型的完整通用签名。

但是,方法的Signature被认为是额外的元数据;您将永远不会看到直接在字节码中引用的一个。取而代之的是,您将看到对方法描述符的引用,该引用类似于递归地应用了通用擦除的签名。与Signature属性不同,方法描述符是强制 javap -v很友善地向你们展示。例如,给定HashMap方法public V put(K, V)

  1. 方法描述符为(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
  2. 通用Signature(TK;TV;)TV;

Signature告诉编译器和您的IDE方法的完整泛型签名,从而可以强制执行类型安全。 descriptor 是在调用站点的字节码中实际引用该方法的方式。例如,给定表达式map.put(0, "zero"),其中mapMap<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)

字节码中有额外的信息用于解码通用信息。