Java - Cast Insertion

Subscribe Send me a message home page tags


During the type erasure process, the compile will insert cast automatically. Unlike the template specialization in C++, Java uses another approach called Code Sharing. The compiler will create one unique byte code representation for each generic type and each instantiation of the generic type will be mapped to this unique representation.

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.

Java_CastInsertion_img_1.png


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

If you have questions about this post, you could find me on Discord.
Send me a message Subscribe to blog updates

Want some fun stuff?

/static/shopping_demo.png