Java Generics Type Casting

0

In this article, I will show you some examples of static and dynamic type casts. I will also show you cases which are not type-safe and compiler issues an “unchecked warning”. These warnings must be properly understood as they prove fatal during runtime.

Type Erasure

Before we discuss casting, we must know this fact that all instances of a generic type share the same runtime type. Java generics is just a language feature to help us catch the type-casting errors at compile time itself.

The compiler removes all the generic and parameterized types by a technique called type erasure. For example,

  1. Our generic Node class, public class Node<N> { will appear as public class Node { in the bytecode.
  2. Likewise, Node<Integer> intNode = new Node<Integer>(2) will become Node intNode = new Node(2).

To demonstrate the cases, I am going to use two class Node and NumberNode.

Node:

package javarticles;


public class Node<E> {
    E mValue;
    Node<E> mNext;
    Node<E> mPrevious;

    public Node(E value) {
        mValue = value;
    }

    public void linkAfter(Node<E> node) {
        node.mPrevious = this;
        node.mNext = mNext;
        if (mNext != null) {
            node.mNext.mPrevious = node;
        }
        mNext = node;
    }

    public E getValue() {
        return mValue;
    }

    public Node<E> getNext() {
        return mNext;
    }

    public Node<E> getPrevious() {
        return mPrevious;
    }        
}

NumberNode extends Node

NumberNode:

package javarticles;

public class NumberNode<N extends Number> extends Node<N>  {
    
    public NumberNode(N value) {
        super(value);
    }

    public int calculateLinkValue() { 
        int totalValue = getValue().intValue();
        if (getNext() != null && getNext().getValue() != null) {
            totalValue +=  getNext().getValue().intValue();
        }
        if (getPrevious() != null && getPrevious().getValue() != null) {
            totalValue +=  getPrevious().getValue().intValue();
        }
        return totalValue;
    }
    
}

We will see few examples of casting where some pass and some don’t.

A cast consists of two types: Static and Dynamic. In the next two sections, we look into each case and analyse the possibilities.

Static cast

Static cast is performed by the compiler at compile time. For example, we can’t cast Node<Double> to Node<Integer>.
The two types are instantiation of the same type Node but for different type arguments Double and Integer.

    
    public void testCast() {
        Node<Double> doubleNode = new Node<Double>(3d);
        Node<Integer> intNode = doubleNode;
    }

Type mismatch: cannot convert from Node<Double> to Node<Integer>

How about checking whether NumberNode<Integer> is an instanceof Node<Integer>?

    
    public void testIsNumberNodeInstanceOfIntNode() {
       NumberNode<Integer> nn = new NumberNode<Integer>(1);
       assertTrue(nn instanceof Node<Integer>);
    }

Compilation fails with error:
Cannot perform instanceof check against parameterized type Node. Use the form Node<?> instead since further generic type information will be erased at runtime
This make sense. instanceof keyword is an operator to test at runtime if an instance is a subtype of a given type. The generic type information will be of no use to instanceof as it will be erased at runtime.

We just need to remove the type info and then do instanceof
assertTrue(nn instanceof Node);

Instantiation of different generic types but for the same type argument can be compatible.

    public void testNumberNodeToNodeLink() {
        NumberNode<Integer> nn = new NumberNode<Integer>(1);
        Node<Integer> n = new Node<Integer>(1);
        n.linkAfter(nn);
    }

The above works, because NumberNode<Integer> and Node<Integer> are for the same argument type Integer and they have subtype-supertype relationship.

Whereas the below fails as the generic types are for different argument types Integer and Double.

    public void testNumberNodeToNodeLink() {
        Node<Integer> n = new Node<Integer>(1);
        NumberNode<Double> dn = new NumberNode<Double>(1d);
        n.linkAfter(dn);
    }

Dynamic Cast

Dynamic part uses the runtime type information and performs a type check at runtime.
In the below test case, a double node is type casted to Object. Compiler allows it as Object is a supertype and we are doing an upcast here, from a subtype to supertype, so it is perfectly legal.
With this typecast, we loose all the parameterized type information. When node object is type-casted again to an integer node, compiler issues warning:
Type safety: Unchecked cast from Object to Node. This is because the target type of the cast Node&ltInteger> is a parameterized type and the compiler cannot guarantee that the object being type casted is of the same type.

    public void testDynamicCast() {
        Node<Double> doubleNode = new Node<Double>(3d);
        Object node = doubleNode;
        Node<Integer> intNode = (Node<Integer>) node;
        Integer intValue = intNode.getValue();
    }

When we run the test case, we get ClassCastException in runtime.
java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.Integer

Note that the upcast is a static cast whereas downcast is the dynamic cast where the compiler has no idea of the actual object the reference is pointing to.

Here is the entire test class.

JavaGenericsTypeCastTests:

package javarticles;

import junit.framework.TestCase;

public class JavaGenericsTypeCastTests extends TestCase {
    public void testLinkOneAndTwo() {
        Node<Integer> one = new Node<Integer>(1);
        Node<Integer> two = new Node<Integer>(2);
        one.linkAfter(two);
        assertEquals(one, two.getPrevious());
        assertEquals(two, one.getNext());
    }

    public void testNodeValue() {
        Node<Integer> one = new Node<Integer>(1);
        Node<Integer> two = new Node<Integer>(2);
        Integer totalValue = one.getValue() + two.getValue();
        assertEquals(3, totalValue.intValue());
    }
    
    public void testGetValue() {
        Double d = 3.2d;
        NumberNode<Double> doubleNode = new NumberNode<Double>(d);
        Double valueInDouble = doubleNode.getValue();
        assertEquals(d, valueInDouble);
    }
    
    //this will fail as string 'Two' is not a number node
    public void donotestNodeValueWithStringNode() {
        NumberNode<Integer> one = new NumberNode<Integer>(1);
        Node<String> two = new Node<String>("Two");
        //one.linkAfter(two);
        assertEquals(3, one.calculateLinkValue());
    }
    
    public void testInsertTwoBetweenOneAndThree() {
        Node<Integer> one = new Node<Integer>(1);
        Node<Integer> three = new Node<Integer>(3);
        one.linkAfter(three);
        assertEquals(one, three.mPrevious);
        assertEquals(three, one.mNext);

        Node<Integer> two = new Node<Integer>(2);
        one.linkAfter(two);

        assertEquals(two, one.mNext);
        assertEquals(three, two.mNext);
        assertEquals(two, three.mPrevious);
        assertEquals(one, two.mPrevious);

        assertEquals(1, one.getValue().intValue());
        assertEquals(2, two.getValue().intValue());
        assertEquals(3, three.getValue().intValue());
    }
    
    public void testStaticCast() {
        Node<Double> doubleNode = new Node<Double>(3d);
        //un-commenting below will throw error Type mismatch: cannot convert from Node<Double> to Node<Integer>
        //Node<Integer> intNode = doubleNode;
    }
    
    public void testDynamicCast() {
        Node<Double> doubleNode = new Node<Double>(3d);
        Object node = doubleNode;
        //warning - Type safety: Unchecked cast from Object to Node<Integer>
        Node<Integer> intNode = (Node<Integer>) node;
        //un-commenting below will throw runtime error
        /*
         * java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.Integer
         */
        //Integer intValue = intNode.getValue();
    }
    
    public void testIsNumberNodeArrayInstanceOfNodeArray() {
        NumberNode[] nna = new NumberNode[]{new NumberNode(1), new NumberNode(2)};
        Node[] na = new Node[]{new Node(1), new Node(2)};
        assertTrue(nna instanceof Node[]);
    }
    
    public void testIsNumberNodeInstanceOfIntNode() {
       NumberNode<Integer> nn = new NumberNode<Integer>(1);
       //un-commenting, below will fail
       //assertTrue(nn instanceof Node<Integer>);
       assertTrue(nn instanceof Node);
    }
    
    public void testNumberNodeToNodeLink() {
        NumberNode<Integer> nn = new NumberNode<Integer>(1);
        Node<Integer> n = new Node<Integer>(1);
        n.linkAfter(nn);
        
        NumberNode<Double> dn = new NumberNode<Double>(1d);
        //un-commenting below will throw compilation error
        //The method linkAfter(Node<Integer>) in the type Node<Integer> is not applicable for the arguments 
        //(NumberNode<Double>)
        //n.linkAfter(dn);
    }    
}

Download the source code

This was an example about Java Generics Type Cast.

You can download the source code here: javaGenericsTypeCast.zip

About Author

Ram's expertise lies in test driven development and re-factoring. He is passionate about open source technologies and loves blogging on various java and open-source technologies like spring. You can reach him at [email protected]

Leave A Reply