Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lazy class cast in Java?

Can someone please enlighten me as to why I don't get a ClassCastException in this snippet? I'm strictly interested into why it isn't working as I was expecting. I don't care at this point whether this is bad design or not.

public class Test {
  static class Parent {
    @Override
    public String toString() { return "parent"; }
  }

  static class ChildA extends Parent {
    @Override
    public String toString() { return "child A"; }
  }

  static class ChildB extends Parent {
    @Override
    public String toString() { return "child B"; }
  }

  public <C extends Parent> C get() {
    return (C) new ChildA();
  }

  public static void main(String[] args) {
    Test test = new Test();

    // should throw ClassCastException...
    System.out.println(test.<ChildB>get());

    // throws ClassCastException...
    System.out.println(test.<ChildB>get().toString());
  }
}

This is the java version, compilation, and run output:

$ java -version
java version "1.7.0_17"
Java(TM) SE Runtime Environment (build 1.7.0_17-b02)
Java HotSpot(TM) 64-Bit Server VM (build 23.7-b01, mixed mode)
$ javac -Xlint:unchecked Test.java
Test.java:24: warning: [unchecked] unchecked cast
    return (C) new ChildA();
               ^
  required: C
  found:    ChildA
  where C is a type-variable:
    C extends Parent declared in method <C>get()
1 warning
$ java Test
child A
Exception in thread "main" java.lang.ClassCastException: Test$ChildA cannot be cast to Test$ChildB
  at Test.main(Test.java:30)
like image 200
Ionuț G. Stan Avatar asked Mar 13 '13 15:03

Ionuț G. Stan


Video Answer


1 Answers

This is due to type erasure. At compile time, when compiling

public <C extends Parent> C get() {
  return (C) new ChildA();
}

simply checks that ChildA is a subtype of Parent and thus the cast won't definitely fail. It does know that you're on shaky ground, given that ChildA might not be assignable to type C, so it issues an unchecked-cast warning letting you know that something could go wrong. (Why does it allow the code to compile, rather than just rejecting it? Language design choice motivated by the need for Java programmers to migrate their old pre-generics code with a minimum of rewriting.)

Now as to why get() doesn't fail: there is no runtime component to the C type parameter; after compilation the type argument simply gets erased out of the program and replaced with its upper bound (Parent). So the call will succeed even if the type argument is incompatible with ChildA, but the first time you actually try to use the result of get() as a ChildB a cast (from Parent to ChildB) will occur and you'll get an exception.

The moral of the story: treat unchecked cast exceptions as errors, unless you can prove to yourself that the cast would always succeed.

like image 176
jacobm Avatar answered Oct 24 '22 11:10

jacobm