public class InterfaceCasting {
private static class A{}
public static void main(String[] args) {
A a = new A();
Serializable serializable
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:
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.
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
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.
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 ....
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.
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;
}