1

Here's my code:

public class A {
    int size = 100;
    public int getSize() {
        return size;
    }
    interface D {

    }
    class B implements D {

    }
    class C {
        int size = 999;
        public int getSize() {
            return size;
        }
    }
    public void test() {
        D d = new B();
        System.out.println(((C) d).getSize());
    }
    public static void main(String[] args) {
        A a = new A();
        a.test();
    }
}

The code compiles without any compiler error. Why wouldn't it have a compiler error. Class C does not have any relationship with the reference type class D and the actual class type B. How did it pass the compiler check for type casting?

wjx008
  • 11
  • 1
  • because its correct B is also a D due to the implementation of D – tung Dec 12 '18 at 19:19
  • 3
    A variable of type D could be an instance of some class that implements D and is also a subclass of C. So the cast can succeed. So it compiles. – JB Nizet Dec 12 '18 at 19:20

2 Answers2

4

From JLS 5.5.1, Reference Type Casting: https://docs.oracle.com/javase/specs/jls/se8/html/jls-5.html#jls-5.5.1

Given a compile-time reference type S (source) and a compile-time reference type T (target), a casting conversion exists from S to T if no compile-time errors occur due to the following rules.

In our case, S = D (interface), T = C (class).

If S is a class type:

S is an interface, so we skip this.

If S is an interface type:

D is an interface, so we use this branch.

If T is an array type, then S must be the type java.io.Serializable or Cloneable (the only interfaces implemented by arrays), or a compile-time error occurs.

T is not an array type, we skip this.

If T is a class or interface type that is not final (§8.1.1), then if there exists a supertype X of T, and a supertype Y of S, such that both X and Y are provably distinct parameterized types, and that the erasures of X and Y are the same, a compile-time error occurs.

We do not have this. We can certainly create one to cause this to happen, but that requires a redefinition of C to fit this model.

Otherwise, the cast is always legal at compile time (because even if T does not implement S, a subclass of T might).

In other words, the compiler has no reason to throw an error, because it is distinctly possible that elsewhere there exists a possible class that can be used to qualify the cast.

For example, you could have this elsewhere:

class E extends C implements D { ... }

Finalizing C causes a compile-time error:

final class C { ... }

Basically, there exists a possibility that there is an implementation of C that can exist that works at runtime which cannot be verified at compile time.

Compass
  • 5,867
  • 4
  • 30
  • 42
  • 1
    Basically what I was saying, but it's good to see the specs to back it up. – xtratic Dec 12 '18 at 19:34
  • 1
    @xtratic I agree. From a human programmer POV, it makes sense, but for the compiler, being able to see the individual decision points gives a concrete understanding of each step going through and why things do as they do, rather than just as we understand. – Compass Dec 12 '18 at 19:39
  • Yep, that's why I was saying the specs were good to see, it's nice to have both perspectives. – xtratic Dec 12 '18 at 20:31
2

Here's my human explanation, see @Compass answer for the details of the specification..

The reason is because you are casting from an interface where the actual class of the instance it holds could match the class you are casting to. d could hold and instance of type C.

Only variable type matters for compile-time while actual type of the instance matters for run-time.

See this code for an example:

class B { ... }
class C { ... }
interface D { ... }

// only variable type matters for compile-time
// the compiler (usually?) doesn't care what's in them
D d = ...;
B b = ...;

// compile error
// `b` can't contain an instance inheriting from `C`.
C c2 = (C) b;

// no compile error
// `d` might contain an instance inheriting from `C`.
C c1 = (C) d;


// it's because of the possibility like below
// that the compiler doesn't complain.
class E extends C implements D { ... }

D d = new E();
C c = (C) d;
xtratic
  • 4,600
  • 2
  • 14
  • 32