Java
When generics were introduced to Java 5.0 back in 2004, they were implemented with type erasure to provide backwards-compatibility with existing bytecode. However, this resulted in an implementation of generics that was largely a compile-time convenience feature for developers as the type information is erased at runtime. To see what this looks like, please consider the following program:
import java.util.ArrayList;
class Main {
public static void printLen(ArrayList<?> list) {
System.out.println(list.size());
}
public static void main(String[] args) {
ArrayList<Integer> ints = new ArrayList<Integer>();
ints.add(1);
ints.add(2);
ints.add(3);
ArrayList<String> strs = new ArrayList<String>();
strs.add("Hello");
strs.add("world");
printLen(ints);
printLen(strs);
}
}
The above program defines two variables using the generic list type, ArrayList
:
ints
: a list ofInteger
valuesstrs
: a list ofString
values
But what do ints
and strs
look like at runtime? Follow the instructions below to find out:
Launch the container:
docker run -it --rm go-generics-the-hard-way
Compile the above program:
javac -g ./05-internals/java/main.java
Load the above program into the Java debugger:
jdb -sourcepath ./05-internals/java -classpath ./05-internals/java Main
Set a breakpoint so the debugger will pause execution after both
ints
andstrs
have been defined and values added to them:stop at Main:34
Run the program:
run
which should stop when the above breakpoint is hit:
Breakpoint hit: "thread=main", Main.main(), line=34 bci=57
34 printLen(ints);
Now that it is loaded into memory, print information about the
ints
variable:dump ints
ints = {
serialVersionUID: 8683452581122892189
DEFAULT_CAPACITY: 10
EMPTY_ELEMENTDATA: instance of java.lang.Object[0] (id=444)
DEFAULTCAPACITY_EMPTY_ELEMENTDATA: instance of java.lang.Object[0] (id=445)
elementData: instance of java.lang.Object[10] (id=446)
size: 3
MAX_ARRAY_SIZE: 2147483639
java.util.AbstractList.modCount: 3
java.util.AbstractCollection.MAX_ARRAY_SIZE: 2147483639
}
Despite being defined at compile-time as an
ArrayList<Integer>
, at runtimeints
has a type ofArrayList<Object>
.Print information about the
strs
variable:dump strs
strs = {
serialVersionUID: 8683452581122892189
DEFAULT_CAPACITY: 10
EMPTY_ELEMENTDATA: instance of java.lang.Object[0] (id=444)
DEFAULTCAPACITY_EMPTY_ELEMENTDATA: instance of java.lang.Object[0] (id=445)
elementData: instance of java.lang.Object[10] (id=448)
size: 2
MAX_ARRAY_SIZE: 2147483639
java.util.AbstractList.modCount: 2
java.util.AbstractCollection.MAX_ARRAY_SIZE: 2147483639
}
Despite being defined at compile-time as an
ArrayList<String>
, at runtimestrs
also has a type ofArrayList<Object>
.Type
quit
to exit the debugger.Type
exit
to stop and remove the container.
In other words, generics in Java do not retain their type information at runtime. What about a more modern VM than the JVM such as Microsoft’s Common Language Runtime (CLR) , the basis for .NET? Let’s find out!
Next: .NET