Reputation: 19
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
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
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