1

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?

  • If you are doubtful in casting. You can use `instanceof` before you proceed casting. – Enzokie Jun 10 '17 at 09:52
  • Read [Chapter 5. Conversions and Promotions](https://docs.oracle.com/javase/specs/jls/se7/html/jls-5.html). – BhalchandraSW Jun 10 '17 at 09:53
  • 2
    What's the concrete type of `a`? It's C, right? Is a C also a B? Yes, since C extends B. So the cast will succeed. It's way simpler to reason about with real classes. Let's say A is Vehicle, B is Car and C is ElectricCar. An ElectricCar is a Car. A Car is a Vehicle. The object that `a` refers to is an ElectricCar. Is it correct to say that this object is a Car? Yes. So the cast succeeds. – JB Nizet Jun 10 '17 at 09:53
  • @Enzokie This is good advice for the real world, but sadly it doesn't help me during an exam if this type of question appears :/ – Tristan Batchler Jun 10 '17 at 09:53
  • @JBNizet That's a great way to think about it! You make a good point that I should mentally consider these poorly named classes as more real-world names. That could definitely help me out. Thanks! – Tristan Batchler Jun 10 '17 at 09:55

2 Answers2

2

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
Community
  • 1
  • 1
Sweeper
  • 213,210
  • 22
  • 193
  • 313
1

A complete understanding of the issues involved in this seemingly simple question will take a long time and requires one to understand the Java language specification, but a decent understanding is perhaps in one's reach. @JBNizet's concretization idea is useful too.

Some terminology and simplification is in order:

  • When you say "converting from a type to another type", the former is called the source type and the latter the target type. Let's limit the discussion to concrete reference types (i.e. classes like A, B, and C) for now. We denote the source type as Source and target type as Target.
  • In an assignment statement involving casting (implicit or explicit) of reference types, variables of source type appear on the right hand side of the = sign and variables of target type on its left hand side. Thus, you do: Target t = (Target) s, where s is a variable of type Source.
  • A variable of a reference type has two kinds of types: a compile-time type and a runtime type.

Now, the applicable rule according to the JLS (after some simplification) is:

For this kind of assignment (Target t = (Target) s) to compile, either Target must be a subclass (or subtype) of Source, or Source must be a subclass of Target.

(the actual, rigorous rule is: If T is a class type, then either |S| <: |T|, or |T| <: |S|. Otherwise, a compile-time error occurs. |S| implies the erasure of S and <: implies an is-subclass-of relation.)

Now, your classes A, B, and C create the following hierarchy: C <: B <: A:

class hierarchy

The main method does something that can be effectively expressed as:

A a = new C(); //as-is (1)
// the intervening B b = new B() does not make any difference
B b = (B) a; //as-is (2) 

Now, going by the applicable rule above, since the type of b (i.e. Target) which is B is a subclass of A (i.e. Source), the assignment in (2) should compile fine, because you are casting from a source class (A) to a target class (B) that are in an is-subclass-of relationship.

The runtime type of the variable a is actually C, according to (1). And since C is a subclass of B, any variable of type C (the source) can always be assigned to a variable of type B (the target) for the cast to succeed at runtime (i.e. for it to not throw ClassCastException). This is called the Liskov Substitution Principle.

Kedar Mhaswade
  • 4,535
  • 2
  • 25
  • 34