Why does Java let you cast to a collection?
JavaCollectionsInterfaceCastingJava Problem Overview
I have a simple foo
class, and I am able to cast to a collection interface (either Map
or List
) without any compiler error.
Note that Foo
class does not implement any interface or extends any other class.
public class Foo {
public List<String> getCollectionCast() {
return (List<String>) this; // No compiler error
}
public Map<String, String> getCollection2Cast() {
return (Map<String, String>) this; // No compiler error
}
public Other getCast() {
return (Other)this; // Incompatible types. Cannot cast Foo to Other
}
public static class Other {
// Just for casting demo
}
}
Why does the Java compiler not return incompatible types error when I try to cast the Foo
class to a collection?
Foo
does not implement Collection
. I would expect an incompatible types error, because given the current Foo
class signature, this cannot be a Collection
.
Java Solutions
Solution 1 - Java
It's not because they're collection classes, it's because they're interfaces. Foo
doesn't implement them, but subclasses of it could. So it's not a compile-time error, since those methods may be valid for subclasses. At runtime, if this
isn't of a class that implements those interfaces, naturally it's a runtime error.
If you change List<String>
to ArrayList<String>
, you'll get a compiler-time error for that, too, since a Foo
subclass could implement List
, but can't extend ArrayList
(since Foo
doesn't). Similarly, if you make Foo
final
, the compiler will give you an error for your interface casts because it knows they can never be true (since Foo
can't have subclasses, and doesn't implement those interfaces).
Solution 2 - Java
The compiler doesn't prevent code from casting a type to an interface, unless it can establish for sure that the relationship is impossible.
If the target type is an interface, then it makes sense because a class extending Foo
can implement Map<String, String>
. However, note that this only works as Foo
is not final
. If you declared your class with final class Foo
, that cast would not work.
If the target type is a class, then in this case it would simply fail (try (HashMap<String, String>) this
), because the compiler knows for certain that the relationship between Foo
and HashMap
is impossible.
For reference, these rules are described in JLS-5.5.1 (T = target type - Map<String, String>
, S = source type - Foo
)
> If T [target type] is an interface type:
> * If S is not a final class (§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 S does not implement T, a subclass of S might).
> * If S is a final class (§8.1.1), then S must implement T, or a compile-time error occurs.
Note the bold-italic comment in the quoted text.
Solution 3 - Java
https://docs.oracle.com/javase/8/docs/api/java/util/List.html
> All Superinterfaces:
> Collection
Foo is a List is a Collection.