Java Type Erasure

In Java, the principle of Generics was first introduced to render tighter checks at the compile time itself and enable Generic programming. Type erasure applies higher-level type constraint checks at the compile term and ignores or discards the element type data at the run time. In Type erasure, the compiler ensures that no additional classes are created, and there is no runtime overhead.

For implementing the generics, the Java compiler will apply the type erasure to the following.

  • Substitute every type parameter in the generic types with their bounds or the objects if the type parameters are unbounded. It means that if we have a type parameter that is not bounded, then it will be substituted with the object at the time of program compilation.

The produced byte code only consists of the ordinary class, method and interface.

  • Add type casting if needed to maintain type safety.
  • Produce the bridge methods that maintain the principle of polymorphism in the generic types that are extended when the type parameters are not bounded.

Working

In general, the generic code that is compiled makes use of the Java.lang.Object every time we talk about E (it can also be any other parameter) - and some data (metadata) informs the Java compiler that it is a generic type. When you compile any code against the generic type or method, the compiler automatically interprets what the programmer is trying to convey( i.e., the Java compiler identifies the type argument of E) and checks at the compile time if the programmer is following the rules. On the other hand, the code emitted again tries to talk in terms of Java.lang.object - the compiler generates the additional casts where required. During the execution, the List and a List are the same; the Java compiler has removed the additional type.

// Here, the type parameter E is unbounded

public class GenericsType<E> 

{

// Here, E gets substituted with the default, 

// which is the Object

E object;

// constructor of the class

GenericsType(E obj)

{

object = obj;

}

// retrieves the object

E getObject()

{

return object;

}

}

In the above code, the type parameter E is unbounded, due to which, after compilation, the Java compiler substitutes it with the Object. Hence, the compiled class won't consist of any type parameter. It is shown in the below code.

// Here, E gets bounded by the Object (Java.lang.Object)

public class GenericsType 

{

// Here, E gets substituted with the default, 

// that is the Object

Object object;

// constructor of the class

GenericsType (Object obj)

{

object = obj;

}

// retries the object

Object getObject()

{

return object;

}

}

Now, go through another example given below. Here, the type parameter E extends the Java.lang.String class.

public class GenericsType1 <E extends String>  

{

// Here, E gets substituted with the String 

// that is the Object

E e;

// constructor of the class

GenericsType1(E obj)

{

e = obj;

}

// retrieves the object

Object getObject()

{

return e;

}

}

After the compilation, the type parameter E gets substituted with the String, which is the object shown in the code below.

// Here, E gets bounded by the Object (Java.lang.String)

public class GenericsType1 

{

// Here, E gets substituted with the String 

// that is the Object

String e;

// constructor of the class

GenericsType1 (String obj)

{

e = obj;

}

// retrieves the object

Object getObject()

{

return e;

}

}

Example 1

Let us go through an example where we have implemented Type erasure in Java.

FileName: TypeErasure.java

// Java program to demonstrate TypeErasure

import java.util.*;

public class TypeErasure {   

// main method

public static void main(String[] args) {

// Creates a list of Strings

List<String> sports = new ArrayList<>();

// Add elements to the list

sports.add("cricket");

sports.add("football");

sports.add("hockey");

sports.add("tennis");

Iterator<String> iterator = sports.iterator();

// Iterates over the elements from the list

while (iterator.hasNext()) {

String s = iterator.next();

System.out.println(s);

}

}

}

Output:

cricket

football

hockey

tennis

Explanation: In the above example, no warnings will be thrown after compilation as we have used the Type Erasure feature in our program.

Example 2

Please go through the example below where we have not used TypeErasure.

FileName: TypeErasure1.java

// Java program to demonstrate TypeErasure

import java.util.*;

public class TypeErasure1 {

// Constructor

public TypeErasure1 () {

}

// main method

public static void main(String[] args) {

// Creates a list of Strings

List sports = new ArrayList();

// Add elements to the list

sports.add("cricket");

sports.add("football");

sports.add("hockey");

sports.add("tennis");

String str;

for (Iterator itr = sports.iterator(); itr.hasNext();

System.out.println(str))

str = (String)itr.next();

}

}

Output:

cricket

football

hockey

tennis

Explanation: It is necessary to specify the list type in the code to validate it at the compile time and show no warnings at the run time. In the above example, a few warnings will be shown after compilation since we are not using Type Erasure in the program. Note that the output of the above example will not change, and only the warning will be shown in the console window.

Raw use of parameterized class 'List'

Raw use of parameterized class 'ArrayList'

Unchecked call to 'add(E)' as a member of raw type 'java.util.List'

Raw use of parameterized class 'Iterator'

Benefits of Type Erasure--------

  • It provides type safety.
  • It offers backward compatibility.
  • It helps avoid Runtime overhead.
  • Only one version of code is produced for all the generic instances, due to which a minimal amount of time is spent in the JIT (Just in Time) compiler, and lesser RAM is consumed for storing the produced code.
  • Improves the performance as the generic type information is removed from the byte code, reducing produced bytecode size and leading to faster execution.
  • There is no need to change the bytecode.