When you write a chain of method calls:
System.out.println(t.second.getClass().getSimpleName());
the compiler effectively expands this to:
TypeOfTSecond tmpTSecond = t.second;
Class<?> clazzTmp = tmp.getClass();
String nameTmp = clazzTmp.getSimpleName();
System.out.println(nameTmp);
Now, if it happens that t.second
is a generic type, the compiler will insert a cast to the type that it things t.second
will be:
Bar tmpTSecond = (Bar) t.second;
So even though you're never accessing any Bar
-specific functionality, you will get the ClassCastException
.
To demonstrate this, here is the bytecode:
public static void main(java.lang.String[]);
Code:
0: invokestatic #2 // Method Test.f:()LTuple;
3: astore_1
4: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
7: aload_1
8: getfield #4 // Field Tuple.second:Ljava/lang/Object;
11: checkcast #5 // class Bar
14: invokevirtual #6 // Method java/lang/Object.getClass:()Ljava/lang/Class;
17: invokevirtual #7 // Method java/lang/Class.getSimpleName:()Ljava/lang/String;
20: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
23: return
Line 8 is where t.second
is pushed onto the stack; line 11 is where the cast to Bar
occurs.
This only comes about because of the raw types used when declaring test.f()
:
static Tuple f(){return new Tuple("test", 8);}
If this were correctly declared as
static Tuple<String, Integer> f(){return new Tuple<>("test", 8);}
then this line
Tuple<String, Bar> t = Test.f();
wouldn't compile. But the use of raw types disables the compiler's type checking, so runtime errors like this cannot be guaranteed to be prevented.
The main take-away lesson is never use raw types.
The secondary lesson is pay attention to your compiler's (or IDE's) warnings. Compiling this code, I was told:
Note: Main.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
and when I recompiled with that flag:
Main.java:19: warning: [unchecked] unchecked call to Tuple(K,V) as a member of the raw type Tuple
return new Tuple("test", 8);
^
where K,V are type-variables:
K extends Object declared in class Tuple
V extends Object declared in class Tuple
Main.java:26: warning: [unchecked] unchecked conversion
Tuple<String, Bar> t = Test.f();
^
required: Tuple<String,Bar>
found: Tuple
2 warnings