0

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();
        System.out.println(t);
      //System.out.println(t.second.getClass().getSimpleName());
    }
}

Thanks in advance.

  • My understanding is that will not work in Java. Your generics aren't being given a type, they're Generics. This would work if you had something like `public final K first;` and it would return String. – Susannah Potts Aug 04 '16 at 19:08
  • `Test.f()` returns a raw type. This error would be easier to catch if you avoided using raw types. The return type of `Test.f()` should be defined as `Tuple` to match the value being returned. This will then cause a compile error on `Tuple t = Test.f()`. Type safety is a powerful feature. – Vince Aug 04 '16 at 19:22
  • You should post the full error: `Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to Bar` - now it's becoming much clearer... – Nir Alfasi Aug 04 '16 at 19:24
  • Read [What is a raw type and why shouldn't we use it](http://stackoverflow.com/q/2770321/3788176). – Andy Turner Aug 04 '16 at 19:36

2 Answers2

3

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
Community
  • 1
  • 1
Andy Turner
  • 137,514
  • 11
  • 162
  • 243
  • Note that the compiler is not *required* to insert a cast of `t.second` to `Bar` in this case. That is does so is implementation-dependent. – newacct Aug 05 '16 at 00:30
0

As per Java Docs, for getClass() method in Object class

The actual result type is Class<? extends |X|> where |X| is the erasure of the static type of the expression on which getClass is called.

In the declaration part, the type is Bar

Tuple<String, Bar> t = Test1.f();

Since there is no parent-child relationship between Integer and Bar, while trying to cast Integer to Bar, you get a ClassCastException (i.e. Integer extends Bar is not correct)

Here is the fix

Tuple<String, Object> t = Test1.f();

Note: It is not encouraged to use raw types. This answer just explains reason behind the failure and the fix for that.

Community
  • 1
  • 1
JavaHopper
  • 5,567
  • 1
  • 19
  • 27
  • Down vote is of no use, unless one the down voter clarifies what the problem is – JavaHopper Aug 05 '16 at 01:05
  • I didn't downvote, but I'm guessing it's because your fix is based on the usage of raw types. If `Test.f()` was declared with type arguments that matched the value being returned (``), you'd get a compile error for using ``. Although it works, it encourages bad design since it avoids the actual problem (lack of type safety) which is shown through the warning message you get from doing this. I personally wouldn't downvote for this reason (it still works), but some people are stricter than others. – Vince Aug 05 '16 at 13:21
  • I get it! So, here at SO, rather than just providing the fix and explaining the reason behind the fix, we also have to suggest best practices. Like you said, I have missed to say that usage of raw types is discouraged. Thanks! – JavaHopper Aug 05 '16 at 15:07
  • Exactly. I personally feel it didn't deserve a downvote cause it still works, but I probably would have upvoted if the answer encouraged better practices. – Vince Aug 05 '16 at 16:18
  • @VinceEmigh, I updated my answer with additional notes – JavaHopper Aug 05 '16 at 16:22