user1232138
user1232138

Reputation: 5541

generic type erasure ClassCastException

package practice;

class Node<T> {

    T data;

    public Node(T data) { this.data = data; }

    public void setData(T data) {
        System.out.println("Node.setData");
        this.data = data;
    }
}

class MyNode extends Node<Integer> {
    public MyNode(Integer data) { super(data); }

    @Override
    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }
}

public class Practice {
    public static void main(String[] s)
    {
        MyNode mn = new MyNode(5);
        Node n = mn;            // A raw type - compiler throws an unchecked warning
        n.setData("Hello");     // Causes a ClassCastException to be thrown.
        Integer x = mn.data;    
    }
}

Why does this code throw exception at n.setData("Hello"); when actually it should throw the exception at Integer x = mn.data;?

http://docs.oracle.com/javase/tutorial/java/generics/bridgeMethods.html

Upvotes: 2

Views: 356

Answers (4)

dcernahoschi
dcernahoschi

Reputation: 15250

I think there is a small mistake in the tutorial: they should have said that the error is thrown at line n.setData("Hello");

That's because when you call n.setData("Hello"); you call the bridge method: n.setData(Object object) on MyNode class, otherwise you would have a compile time error.

But the bridge method looks like this:

// Bridge method generated by the compiler
//
public void setData(Object data) {
    setData((Integer) data);
}

You see that the bridge tries to invoke the setData(Integer anInt) with a cast on the data. And this cast fails because you provided a String.

What's also interesting at this example is that you get no compile time error when using @Override annotation even that you technically don't override, but the compiler overrides later. The annotation is very very useful here to inform that the setData(Object object) will be called on MyNode not on Node. This explains why you don't see any of the System.out messages.

Upvotes: 1

Varun Achar
Varun Achar

Reputation: 15129

Because the type of n is actually Integer, which it received from mn.

It's the same principle as List list = new ArrayList();

The list here is actually an ArrayList, but only referenced using the List interface. Similarly, even though n is a Node, it's implementation is a MyNode which only accepts Integer. That's why you can put a String in setData()

After reading the edit example

The page says that a bridge method is created as follows :

class MyNode extends Node {

    // Bridge method generated by the compiler
    //
    public void setData(Object data) {
        setData((Integer) data);
    }

    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }

    // ...
}

The bridge method does the following

public void setData(Object data) {
            setData((Integer) data);
        }

Here it is trying to cast data which is the String "Hello" into an Integer. Hence the error is thrown.

Upvotes: 0

Reimeus
Reimeus

Reputation: 159864

You have already defined the type T for Node by calling

MyNode mn = new MyNode(5);

namely Integer type. By calling n.setData("Hello"); you are attempting to pass in a String variable for an Integer field. Node n is merely a reference for the Integer typed Node mn.

Upvotes: 1

Łukasz Tomaszewski
Łukasz Tomaszewski

Reputation: 605

Because you are trying to set String value to Integer variable.

Upvotes: 2

Related Questions