tags:

views:

148

answers:

8
public class InterfaceCasting {

    private static class A{}

    public static void main(String[] args) {
        A a = new A();
        Serializable serializable = new Serializable(){};
        a = (A)serializable;
    }

}

Compilation succeed but Runtime exception

Exception in thread "main" java.lang.ClassCastException: InterfaceCasting$1 cannot be cast to InterfaceCasting$A

WHY COMPILATION SUCCEED? Compiler must known that serialiazable is not A?

A: 

Serializable is NOT an A, so it throws ClassCastException.

卢声远 Shengyuan Lu
That is correct but not what the OP asked.
cherouvim
A: 

It can't know that because the compile time type of serializable is Serializable.

To illustrate, consider this:

private static class A{}
private static class B implements Serializable {}

Serializable serializable = new B();
A a = (A)serializable;

this is exactly like your question, it compiles.

private static class A{}
private static class B implements Serializable {}

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

this does not compile because b is not an A.

cherouvim
+13  A: 

As you point out, this will compile:

interface MyInterface {}

class A {}

public class InterfaceCasting {
    public static void main(String[] args) {
        MyInterface myObject = new MyInterface() {};
        A a = (A) myObject;
    }
}

This however, will not compile:

interface MyInterface {}

class A {}

public class InterfaceCasting {
    public static void main(String[] args) {
        A a = (A) new MyInterface() {}; // javac says: "inconvertible types!"
    }
}

So, what's going on here? What's the difference?

Well, since MyInterface is simply an interface, it could very well be implemented by a class that extends A, in which case the cast from MyInterface to A would be legal.


This code for instance, will succeed in 50% of all executions, and illustrates that the compiler would need to solve possibly undecidable problems in order to always "detect" illegal casts at compile time.

interface MyInterface {}

class A {}

class B extends A implements MyInterface {}

public class InterfaceCasting {
    public static void main(String[] args) {
        MyInterface myObject = new MyInterface() {};
        if (java.lang.Math.random() > 0.5)
            myObject = new B();
        A a = (A) myObject;
    }
}
aioobe
+4  A: 
Serializable serializable;
a = (A)serializable;

As for the compiler, the variable serializable can contain any object that implements Serializable, which includes subclasses of A. So it assumes that you know that the variables indeed contains an A object and allows that line.

Amarghosh
No, it may **not** include `A`. The compiler knows for sure that `A` doesn't implement `Serializable`.
aioobe
@aioobe yes, but a subclass of `A` may
seanizer
you should clarify this by writing "which includes subclasses of A".
aioobe
@aioobe done...
Amarghosh
+1  A: 

The compiler is not smart enough to trace the origins of serializable and realize that it can never be of type A. It really only evaluates the line:

a = (A)serializable;

and sees that serializable a reference of type Serializable but it may reference a class that also is of type A. The actual class that serializable references is not known until run-time.

In this trivial case, we know that this cast will never succeed, but in general this is left as a run-time issue as the different code paths that may lead to a casting are (in theory) infinite.

If you want to avoid this issue at run-time you could test for it..

if (serializable instanceof A) {
    a = (A)serializable;
} else ....
Kris
A: 

While I don't know the correct answer, it's usually not a good idea to cast an interface to a class, for several reasons.

a) An interface defines a contract, it guarantees behavior. A class may define more than this contract, usage of the other methods may have unexpected side-effects and break APIs. E.g. when a method is passed a list and you find out the passed object is actually a LinkedList and you cast it and use the Queue based methods it also defines, you are breaking the API.

b) also, the object with the interface may not be a "real" object at runtime, but perhaps a service proxy created around the original object by a library such as Spring or EJB. Your cast will fail in those cases.

If you absolutely must cast, never do it without an instanceof check:

if(myServiceObject instanceof MyServiceObjectImpl){
    MyServiceObjectImpl impl = (MyServiceObjectImpl) myServiceObject;
}
seanizer
A: 

The detailed rules for compile-time legality of a casting conversion of a value of compile-time reference type S to a compile-time reference type T are as follows:
[...]
If S is an interface type:
- If T is an array type, [...].
- If T is a 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. Otherwise, the cast is always legal at compile time (because even if T does not implement S, a subclass of T might).

Source :
JLS : Conversions and Promotions

Colin Hebert
+2  A: 

Java language specification states, that:

Some casts can be proven incorrect at compile time; such casts result in a compile-time error.

And later on the show The detailed rules for compile-time legality of a casting conversion of a value of compile-time reference type S to a compile-time reference type T - beware, they are very complex and hard to understand.

The interesting rule is:

  • If S is an interface type:
    • If T is a 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. Otherwise, the cast is always legal at compile time (because even if T does not implement S, a subclass of T might).

In your example, it's perfectly clear, that the cast is illegal. But consider this slight change:

public class InterfaceCasting {

    private static class A{}
    private static class B extends A implements Serializable{}

    public static void main(String[] args) {
        A a = new A();
        Serializable serializable = new B(){};
        a = (A)serializable;
    }    
}

Now a cast from a Serializable to A is possible at runtime and this shows, that in those cases, it's better left to the runtime to decide if we can cast or not.

Andreas_D
yeah , I got it nowThanks everybody for your help
komenan