I decompiled the Map class using javap. The class definition still shows the presence of generic types K and V. This should have been erased by the concept of type erasure. Why does that not happen ?
./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:
                Java For Testers Type erasure is a process in which compiler replaces a generic parameter with actual class or bridge method. In type erasure, compiler ensures that no extra classes are created and there is no runtime overhead.
- Erasure is a type of alteration in document. It can be classified as chemical erasure and physical erasure.
What is the following method converted to after type erasure? public static <T extends Comparable<T>> int findFirstGreaterThan(T[] at, T elem) { // ... } Answer: public static int findFirstGreaterThan(Comparable[] at, Comparable elem) { // ... }
Type-erasure simply means "erasing" a specific type to a more abstract type in order to do something with the abstract type (like having an array of that abstract type).
If generic signature information were completely erased, it would not be possible to consume generic types or methods unless you also had the source code. Think about it: in order to use generics effectively, the compiler must know that a type or method is generic, and it must know the number, position, and bounds of the generic parameters.
To that end, javac emits what's called a Signature attribute on types and methods which are themselves generic, or whose signatures contain type variables or instantiations of other generic types.
For a generic type like Map<K, V>, the class definition will emitted with a Signature attribute describing:
For the Map interface, the Signature value looks like this:
<K:Ljava/lang/Object;V:Ljava/lang/Object;>Ljava/lang/Object;
You can see this attribute in javap -v at the very end of the output, on the line following the closing }. To see what a more complete generic signature looks like, take a look at the HashMap class, which has a generic base class and implements multiple interfaces:
<K:Ljava/lang/Object;V:Ljava/lang/Object;>Ljava/util/AbstractMap<TK;TV;>;Ljava/util/Map<TK;TV;>;Ljava/lang/Cloneable;Ljava/io/Serializable
From this signature, the compiler knows the following about type HashMap:
K and V, both of which extend java.lang.Object.java.util.AbstractMap<K, V>.  To clarify, K and V here refer to the parameters defined by HashMap (not AbstractMap).java.util.Map<K, V>, java.lang.Cloneable, and java.io.Serializable.Methods may also have Signature attributes, but in the case of methods, the signature describes:
However, a method's Signature is considered extra metadata; you will never see one referenced directly in bytecode.  Instead, you will see references to the method descriptor, which is similar to a signature that has had generic erasure applied recursively.  Unlike Signature attributes, method descriptors are mandatory.  javap -v is kind enough to show you both.  For example, given the HashMap method public V put(K, V):
(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;.Signature is (TK;TV;)TV;.The Signature tells the compiler and your IDE the full generic signature of the method, enabling enforcement of type safety.  The descriptor is how the method is actually referenced in the bytecode at a call site.  For example, given the expression map.put(0, "zero") where map is a Map<Integer, String>, the instruction sequence would be something like:
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;
Note how there is no generic information retained.  Limited type safety is enforced at runtime by the insertion of checkcast instructions, which perform runtime casts.  For example, a call to map.get(0) on a Map<Integer, String> would include an instruction sequence similar to:
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;
Thus, even though the Map type is fully erased at the call site, the emitted bytecode ensures that any value retrieved from a Map<Integer, String> is actually a String, and not some other Object.
It's important to stress that, like most metadata in a classfile, Signature attributes are completely optional.  And while javac will emit them when necessary, it is possible for them to be stripped out by post processors like bytecode optimizers and obfuscators.  This would, of course, make it impossible to consume generics in the manner intended.  If, for example, you were to strip out the Signature attributes in java/util/Map.class, you could only consume Map as a non-generic class equivalent to Map<Object, Object>, and you would have to handle type checking yourself.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With