Andrew Sushko Andrew Sushko - 2 months ago 6
Java Question

Java Generics "upcast" to unparameterized type

Why I get ClassCastException only when uncomment third statement in Main.main() ? And no exceptions, but well executed first and second statements?

public class Tuple<K, V> {
public final K first;
public final V second;

public Tuple(K first, V second) {
this.first = first;
this.second = second;

@Override public String toString() {
return "Tuple{" + "first = " + first + ", second = " + second + '}';

class Test { static Tuple f(){return new Tuple("test", 8);} }

class Bar {}

class Main{
public static void main(String[] args) {
Tuple<String, Bar> t = Test.f();

Thanks in advance.


When you write a chain of method calls:


the compiler effectively expands this to:

TypeOfTSecond tmpTSecond = t.second;
Class<?> clazzTmp = tmp.getClass();
String nameTmp = clazzTmp.getSimpleName();

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[]);
       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: uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

and when I recompiled with that flag: 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 warning: [unchecked] unchecked conversion
    Tuple<String, Bar> t = Test.f();
  required: Tuple<String,Bar>
  found:    Tuple
2 warnings