2

Suppose I have three classes A, B and C.

C extends B and B extends A. A implements an interface InterfaceA B implements interfaces InterfaceB1 and InterfaceB2.

What is the best way to find all possible combinations of downcasting?

What I mean is?

Suppose we have: B b = new B(); InterfaceA i = (InterfaceA)(A)(B) b.

How can we easily know if this compiles and if it will cause a classcastException without an IDE?

I know how objects and references work and have a decent understanding of polymorphism in Java.

I started to draw a sketch of the class and interface structure.

EDIT: I know my example is not correct but I always struggle when interfaces join the story.

Ayoub Rossi
  • 410
  • 5
  • 18

2 Answers2

0

Let's deal with "whether or not it compiles" first.

The key to determining this is to look at the compile time type, i.e. the declared type of the variables.

Generally,

  • Casting from a compile time interface type to an interface type always compiles.
  • Casting from a compile time interface type to a class only compiles if any of the following is true:
    • the class is non-final
    • the class implements the interface
  • Casting from a compile time class type to an interface type only compiles if any of the following is true:
    • the class is non-final
    • the class implements the interface
  • Casting from a compile time class type to another class type only compiles if one of them inherits from the other class.

To determine whether the cast succeeds at runtime, the key is to look at the runtime type of the object stored in the variable. For example:

A a = new B();

The compile time type of a is A, but the runtime type is B.

Generally, casting from runtime class type T to a class or interface type U only succeeds if T inherits from U or implements U.


For future readers,

If instead you want to know how to check if a cast will succeed using code, you should refer to this question.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • your explanation is already quite nice. however, for completeness i think you might want to add the practical side of things, i.e. "foo.getClass().isAssignableFrom(Bar.class)" and "foo instanceof Bar" – rmalchow Jan 20 '19 at 13:13
  • @rmalchow That is already covered by another question. OP here only wants to know how to do this with pen and paper. – Sweeper Jan 20 '19 at 13:17
  • did you edit this? yeah, i guess a reference to another answer is fine. – rmalchow Jan 20 '19 at 13:19
  • Thanks for your explanation. If I understood it well, InterfaceB2 b2 = (InterfaceB2) a;, is the same as InterfaceB2 b2 = (InterfaceB2) (B) (A) a;? – Ayoub Rossi Jan 20 '19 at 14:13
  • @AyoubRossi If by `a` you mean the `a` in `A a = new B();`, then yes, the two statements do the same thing, converting from `A` to `InterfaceB2`. In other words, the intermediate casts to `B` and `A` are redundant. – Sweeper Jan 20 '19 at 14:15
  • Thanks, one last question. Suppose we have a class B that implements an interface Ib and B b = new B(); Ib interface = b wouldn't work. It should be Ib interface = (Ib) b. What is the reason for that? – Ayoub Rossi Jan 20 '19 at 14:23
  • `Ib interface = b;` wouldn't work _not_ because you need a cast, but because `interface` is a reserved word, and so can't be the name of a variable. `Ib ib = b;` should compile. @AyoubRossi – Sweeper Jan 20 '19 at 14:25
  • Are there cases where it is mandatory to write the two casts or is only the first one required and the others redundant? – Ayoub Rossi Jan 20 '19 at 14:31
  • @AyoubRossi You almost always only need one cast. I said "almost" because there might be some really really obscure cases out there that I am not ware of. I personally have not encountered such cases. Also note that sometimes adding more casts will cause an exception. Suppose `b` is of runtime type `B`, `(InterfaceB1)b` will succeed, but `(InterfaceB1)(C)b` will crash with a `ClassCastException`, because it tries to cast `b` to `C` first. – Sweeper Jan 20 '19 at 14:36
0

First of all, in your scenario the following is not a downcasting at all because in described hierarchy B is B, B is A and B is InterfaceA. So any of these casts can be omitted.

InterfaceA i = (InterfaceA)(A)(B) b;

Then, answering your question. Downcasting will be compilable then and only then when it is possible to be successful in runtime. Let's introduce additional class D, that extends class A and implements InterfaceB1.

A a = new A();
InterfaceB1 b1 = (InterfaceB1) a; // compilable as A reference can contain object of class B that implements InterfaceB1
InterfaceB2 b2 = (InterfaceB2) a; // compilable as A reference can contain object of class B that implements InterfaceB2
b1 = (InterfaceB1) b2; // compilable as InterfaceB2 reference can contain object of class B that implements both InterfaceB1, InterfaceB2
B b = (B) a; // compilable as A reference can contain object of class B
C c = (C) a; // compilable as A reference can contain object of class C
D d = (D) a; // compilable as A reference can contain object of class D
d = (D) b1; // compilable as InterfaceB1 reference can contain object of class D in runtime

b = (B) d; // not compilable since D reference can NEVER contain object of class B in runtime
c = (C) d; // not compilable since D reference can NEVER contain object of class D in runtime

As for causing ClassCastException it is always about what actually is contained in your object reference.

A a1 = new A();
A a2 = new B();

InterfaceB1 b1 = (InterfaceB1) a1; // compiles but causes ClassCastException as A cannot be cast to InterfaceB1
InterfaceB1 b2 = (InterfaceB1) a2; // compiles and runs just normally as B can be cast to InterfaceB1