Generics erasure

The use of Generics allows a type or method to operate on objects of different types while providing compile-time type safety. However, the JVM knows nothing about type parameters or generics as the compiler erases type parameters when creating bytecode.

According to https://docs.oracle.com/javase/tutorial/java/generics/erasure.html, type erasure aim is

“Type erasure ensures that no new classes are created for parameterized types; consequently, generics incur no runtime overhead.”

In the following examples, we will explore the bytecode resulting of compiling a class with generics and how that code enforces type safety at runtime.

Example 1

Given the class

public class MyClass {
    public void print(List<String> list){
    }
}

 

 

After compiling

javac -cp myclasses/ -g -d myclasses/ src/main/java/fjab/generics/MyClass.java

 

 

the resulting bytecode does not keep the parametized type as the descriptor of the method is just a raw type.

javap -v myclasses/fjab/generics/MyClass.class
public void print(java.util.List<java.lang.String>);
    descriptor: (Ljava/util/List;)V
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=2, args_size=2
         0: return
      LineNumberTable:
        line 11: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  this   Lfjab/generics/MyClass;
            0       1     1  list   Ljava/util/List;
      LocalVariableTypeTable:
        Start  Length  Slot  Name   Signature
            0       1     1  list   Ljava/util/List<Ljava/lang/String;>;
    Signature: #18                          // (Ljava/util/List<Ljava/lang/String;>;)V

 


Example 2

Given the class

public class MyClass {

    public void print(List<String> list){}
    public void print(List<Integer> list){}
}

 

 

When compiling, the compiler complains because, after erasure, there are two methods with the same signature:

javac -cp myclasses/ -g -d myclasses/ src/main/java/fjab/generics/MyClass.java
src/main/java/fjab/generics/MyClass.java:11: error: name clash: print(List<Integer>) and print(List<String>) have the same erasure
    public void print(List<Integer> list){}
                ^
1 error


Example 3

Despite type erasure, the class file keeps knowledge of the original method signature and the compiler uses that information to enforce type safety.

Given these classes

public class MyClass {

    public void print(List<String> list){}
}

public class MyApp {

    public static void main(String[] args){

        List<Integer> src = new ArrayList<>();
        MyClass.print(src);
    }
}

 

 

Firstly MyClass is compiled successfully

javac -cp myclasses/ -g -d myclasses/ src/main/java/fjab/generics/MyClass.java

 

 

Then, MyApp fails to compile as ‘src’ is not a List of Strings but Integers.

javac -cp myclasses/ -g -d myclasses/ src/main/java/fjab/generics/MyApp.java
src/main/java/fjab/generics/MyApp.java:14: error: incompatible types: List<Integer> cannot be converted to List<String>
        MyClass.print(src);
                      ^
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error

 

 

Since the classes are compiled separately, when compiling MyApp.java the compiler needs to access MyClass.class to know that the types are incompatible.


Example 4

So the compiler ensures type safety. What if MyClass is changed after the initial compilation and the types are not compatible?

Let’s start with these classes:

public class MyClass {

    public static void print(List<String> list){
        list.get(0).getBytes();
    }
}

public class MyApp {

    public static void main(String[] args){

        List<String> src = new ArrayList<>();
        src.add("hello world");
        MyClass.print(src);
    }
}

 

 

and get them compiled successfully

javac -cp myclasses/ -g -d myclasses/ src/main/java/fjab/generics/*.java

 

 

Now, if ‘MyClass’ is changed for the method ‘print’ to accept a List of Integers

public class MyClass {

    public static void print(List<Integer> list){
        list.get(0).intValue();
    }
}

 

 

It gets compiled successfully

javac -cp myclasses/ -g -d myclasses/ src/main/java/fjab/generics/MyClass.java

 

 

However, when MyApp is executed, the application throws a ClassCastException

java -cp myclasses/ fjab.generics.MyApp
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
   at fjab.generics.MyClass.print(MyClass.java:11)
   at fjab.generics.MyApp.main(MyApp.java:15)

 

 

Examining the bytecode, it is clear why. The application tries to cast the elements of the List to the expected type and when it finds a String, it throws the Exception. This way, despite the type erasure, the bytecode enforces type safety at runtime.

javap -v myclasses/fjab/generics/MyClass.class
public static void print(java.util.List<java.lang.Integer>);
    descriptor: (Ljava/util/List;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: iconst_0
         2: invokeinterface #2,  2            // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
         7: checkcast     #3                  // class java/lang/Integer
        10: invokevirtual #4                  // Method java/lang/Integer.intValue:()I
        13: pop
        14: return
      LineNumberTable:
        line 11: 0
        line 12: 14
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      15     0  list   Ljava/util/List;
      LocalVariableTypeTable:
        Start  Length  Slot  Name   Signature
            0      15     0  list   Ljava/util/List<Ljava/lang/Integer;>;
    Signature: #21                          // (Ljava/util/List<Ljava/lang/Integer;>;)V

 

 


 

Technical details

This examples have been run on OS X 10.11.1 using:

java -version
java version “1.8.0_45”
Java(TM) SE Runtime Environment (build 1.8.0_45-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.45-b02, mixed mode)

javac -version
javac 1.8.0_45

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.