Search code examples
javacastingsubtyping

What are some hardfast "rules" for casting with subtypes?


I was attempting some practice exams for my Java final coming up and I came across this question.

Consider the following class definitions and indicate whether 'Test.main()' would compile successfully. If it does compile, indicate whether it would run successfully or if not, indicate what Exception would be thrown.

public class A {
    public int method(int[] a) {...}
}
public class B extends A {
    @Override
    public int method(int[] a) {...}
}
public class C extends B {
    @Override
    public int method(int[] a) {...}
    public void otherMethod() {...}
}
public class Test {
    public static void main(String[] args) {
        A a = new C();
        B b = new B();
        b = (B) a;
    }
}

I thought that Test.main() would compile but throw a runtime exception due to the fact that a is of actual type C and we are trying to cast it to type B. This is not the case as the answers say this is fine.

I'm pretty confused about the rules of casting where there is a hierarchy deeper than 2 levels involved. The lecture slides don't really have this kind of information!

So what are some hardfast "rules" to keep in mind if this type of question pops up on the exam?


Solution

  • When there is a complicated hierarchy, try drawing it out so it's clearer:

    A <- B <- C
    

    I thought that Test.main() would compile but throw a runtime exception due to the fact that a is of actual type C and we are trying to cast it to type B.

    a's underlying type is C. However, C is convertible to B and A because C inherits from B and B inherits from A.

    Basically, a general rule for whether a cast of reference types succeeds or not is as follows:

    For any cast in the below format:

    (X)Y

    where X is a reference type and Y is a variable of a reference type, the cast will succeed at runtime if you can go from Y's underlying type to X in the inheritance hierarchy by only going along the directions of the arrows.

    Say we have this code:

    A a = new A();
    B b = (B)a;
    

    This will fail because we need to go against the direction of the arrows to go from A to B


    How do you know whether a cast will fail at compile time, then?

    This is very easy. Just check whether Y's variable type (not underlying type!) is unrelated to X.

    For example:

    // these two types are unrelated
    class Foo {}
    class Bar {}
    
    // ...
    Foo f = new Foo();
    Bar b = (Bar)f; // fails to compile
    

    However, if Y's variable type is related to X, it compiles fine:

    Object f = new Foo();
    Bar b = (Bar)f; // Bar inherits from Object, so compiles fine.
                    // But since Foo (f's underlying type) is unrelated to Bar
                    // this crashes at runtime