If we think of the generic type, there are actually two aspects of it:
1. The definition of the generic type.
2. The place where we use the generic type.
The type erasure process involves both them. In the definition of the generic type, the parameter type will be removed after the type erasure. For example in our case, the parameter type is a unbounded one, therefore according to the Java documentation, the parameter type
T
will be replaced by Object
. This process allows the compiler to reduce the different instantiations to one single representation. As we can imagine, the parameter type information is lost in this process and there must be another process that re-build or at least retain this information. This is where the cast insertion comes into the picture. In the place where a generic type is used, the compiler will also remove the parameter type from the code as well and the generic type becomes a raw type. As the compiler processes the parameter type, it will have the knowledge to infer the actual type of the parameter type. For example, if we have a class defined as
MyCollection<T>
and we create one instance MyCollection<Integer>
instance, the compiler will be able to know that for the variable instance
, the parameter type T is equal to Integer. Assume MyCollection also has a method value()
that returns an object with type T. In the place where instance.value()
is used, the compiler will know the returned value should be of type T = Integer
. Therefore, the compiler will insert a cast for us.Source code represents a certain amount of information and there are two different types of it. One type of the information is the information that is available to compiler. For example, compiler knows the type of a variable and hence can check whether the code is valid or not. On the other hand, there some knowledge that may not be available to compiler. Usually this type of knowledge is expressed by casting. Therefore, from the information point of view, the type erasure process and the cast insertion process are two components of a higher level process that transfer the information from compiler-known domain to some runtime domain. The end result is that the conservation of information is achieved.

In the section below, we will present a simple code that defines a generic type. We will also present the decompiled the
.class
file. This file will allows us to know what changes are introduced by compiler.Source Code
public class CastInsertionDemo<T> {
private T value;
public CastInsertionDemo(T value) {
this.value = value;
}
public T value() { return value; }
public static void main(String[] args) {
CastInsertionDemo<String> stringInstance = new CastInsertionDemo<>("StringInstance" );
CastInsertionDemo<Integer> integerInstance = new CastInsertionDemo<>(200);
//String s = integerInstance.value(); // This does not work. It shows that at compile time,
// the compiler can
// infer the type T
int x = integerInstance.value();
long y = integerInstance.value();
}
}
Decompiled Class File
public class CastInsertionDemo<T> {
private T value;
public CastInsertionDemo(T value) {
this.value = value;
}
public T value() {
return this.value;
}
public static void main(String[] args) {
new CastInsertionDemo("StringInstance");
CastInsertionDemo<Integer> integerInstance = new CastInsertionDemo(200);
int x = (Integer)integerInstance.value();
long y = (long)(Integer)integerInstance.value();
}
}
As we can see in the last statement, the compiler inserts two casts. The inner cast is to cast the returned value of the method to Integer
. As mentioned before, the compiler can infer that the type parameter T
is Interger. As the value method returns a type T
object, it is reasonable for compiler to insert a cast to the specified type. The outter cast is to cast value to long
. This is also an reasonable action because we know that the value of y should have type long. It should not do any harm to insert this cast.
----- END -----
©2019 - 2023 all rights reserved