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:

  1. import java.util.ArrayList;
  2. class Main {
  3. public static void printLen(ArrayList<?> list) {
  4. System.out.println(list.size());
  5. }
  6. public static void main(String[] args) {
  7. ArrayList<Integer> ints = new ArrayList<Integer>();
  8. ints.add(1);
  9. ints.add(2);
  10. ints.add(3);
  11. ArrayList<String> strs = new ArrayList<String>();
  12. strs.add("Hello");
  13. strs.add("world");
  14. printLen(ints);
  15. printLen(strs);
  16. }
  17. }

The above program defines two variables using the generic list type, ArrayList:

  • ints: a list of Integer values
  • strs: a list of String values

But what do ints and strs look like at runtime? Follow the instructions below to find out:

  1. Launch the container:

    1. docker run -it --rm go-generics-the-hard-way
  2. Compile the above program:

    1. javac -g ./05-internals/java/main.java
  3. Load the above program into the Java debugger:

    1. jdb -sourcepath ./05-internals/java -classpath ./05-internals/java Main
  4. Set a breakpoint so the debugger will pause execution after both ints and strs have been defined and values added to them:

    1. stop at Main:34
  5. Run the program:

    1. run

    which should stop when the above breakpoint is hit:

    1. Breakpoint hit: "thread=main", Main.main(), line=34 bci=57
    2. 34 printLen(ints);
  6. Now that it is loaded into memory, print information about the ints variable:

    1. dump ints
    1. ints = {
    2. serialVersionUID: 8683452581122892189
    3. DEFAULT_CAPACITY: 10
    4. EMPTY_ELEMENTDATA: instance of java.lang.Object[0] (id=444)
    5. DEFAULTCAPACITY_EMPTY_ELEMENTDATA: instance of java.lang.Object[0] (id=445)
    6. elementData: instance of java.lang.Object[10] (id=446)
    7. size: 3
    8. MAX_ARRAY_SIZE: 2147483639
    9. java.util.AbstractList.modCount: 3
    10. java.util.AbstractCollection.MAX_ARRAY_SIZE: 2147483639
    11. }

    Despite being defined at compile-time as an ArrayList<Integer>, at runtime ints has a type of ArrayList<Object>.

  7. Print information about the strs variable:

    1. dump strs
    1. strs = {
    2. serialVersionUID: 8683452581122892189
    3. DEFAULT_CAPACITY: 10
    4. EMPTY_ELEMENTDATA: instance of java.lang.Object[0] (id=444)
    5. DEFAULTCAPACITY_EMPTY_ELEMENTDATA: instance of java.lang.Object[0] (id=445)
    6. elementData: instance of java.lang.Object[10] (id=448)
    7. size: 2
    8. MAX_ARRAY_SIZE: 2147483639
    9. java.util.AbstractList.modCount: 2
    10. java.util.AbstractCollection.MAX_ARRAY_SIZE: 2147483639
    11. }

    Despite being defined at compile-time as an ArrayList<String>, at runtime strs also has a type of ArrayList<Object>.

  8. Type quit to exit the debugger.

  9. 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