Java Generic Cast Error

I've got an issue in java that I don't understand, could someone explain me that strange behaviour?

my code :

package com.test;

import junit.framework.TestCase;

public class MyTest extends TestCase{

    class Container<I, J extends I> {
        protected J data;

        public J getData() {
            return data;
        }

        @SuppressWarnings("unchecked")
        public void setData(I data) {
            try {
                this.data = (J) data;
            } catch (ClassCastException e) {
                System.err.println("Cast" + e);
            }
        }
    }

    class A {
        public String a = "A";
    }

    class B extends A {
        public String B = "B";
    }

    class C extends A {
        public String C = "C";
    }

    public void test1() throws Exception{
        Container<A, B> container = new Container<>();
        container.setData(new C());
        assertNull(container.getData());
    }
}

I expected this test to pass, but I've got this following error :

junit.framework.AssertionFailedError: Expected: <null> but was: com.test.MyTest$C@5c228bbd
    at junit.framework.Assert.fail(Assert.java:57)
    at junit.framework.Assert.assertTrue(Assert.java:22)
    at junit.framework.Assert.assertNull(Assert.java:277)
    at junit.framework.Assert.assertNull(Assert.java:268)
    at junit.framework.TestCase.assertNull(TestCase.java:438)
    at com.test.MyTest.test1(MyTest.java:39)

How is it possible that the container can contain a C class into a B class?

Also, if I try to get the B value from the data, I've got a ClassCastException...

public void test1() throws Exception{
    Container<A, B> container = new Container<>();
    container.setData(new C());
    System.out.println(container.getData().B);
}

Executing that test gives this following error :

java.lang.ClassCastException: com.test.MyTest$C cannot be cast to com.test.MyTest$B
    at com.test.MyTest.test1(MyTest.java:39)

Upvotes: 0

Views: 97

Answers (2)

Dici
Dici

Reputation: 25950

You shouldn't suppress compile-time warnings, otherwise you cannot be surprised that your code has a strange runtime behaviour. Your cast in setData is unchecked because of type erasure, it means it will never fail. If you want a J to be passed, just use J in the signature instead of I. The type consistency will be ensured compile-time.

By the way, assertNull was not the right way to test this behaviour. You should have let the ClassCastException propagate and use JUnit annotations :

@Test(expected = ClassCastException.class)
public void test1() {
    Container<A, B> container = new Container<>();
    container.setData(new C());
}

Upvotes: 3

Paolo
Paolo

Reputation: 22638

This is because of type erasure. The generic constaints are only for compile-time checks (which your code passes). Once your code is compiled, the I and J generic types are substituted for Object in the resulting code.

So the running code really looks like:

public void setData(Object data) {
    try {
        this.data = (Object) data; //Obviously always works!
    } catch (ClassCastException e) {
        System.err.println("Cast" + e);  //This line never reached
    }
}

Upvotes: -1

Related Questions