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;
}