In the following code, a dowcast to an apparently incompatible type passes compilation:
public class Item {
List<Item> items() { return asList(new Item()); }
Item m = (Item) items();
}
Item
and List<Item>
are disparate types so the cast can never succeed. Why did the compiler allow this?
A List<Item>
could very well be an Item. See for example:
public class Foo extends Item implements List<Item> {
// implement required methods
}
A cast tells the compiler: "I know you can't be sure that this is a object of type Item, but I know better than you, so please compile". The compiler will only refuse to compile that if it's impossible for the returned object to be an instance of Item (like, for example, Integer
can't ever be a String
)
At runtime, the type of the actual object returned by the method will be checked, and if it's not actually an object of type Item, you'll get a ClassCastException.
The relevant specification entry can be found here. Let S be the source, and T the target; in this case, the source is the interface, and the target is a non-final type.
If S is an interface type:
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.
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).
It took a few readings to get this straight, but let's start from the top.
- The target is not an array, so this rule doesn't apply.
- Our target does not have a parameterized type associated with it, so this rule would not apply.
- This means that the cast will always be legal at compile time, for the reason illustrated by JB Nizet: our target class may not implement the source, but a subclass might.
This also means that it won't work if we cast to a final class that doesn't implement the interface, per this snippet:
If S is not a parameterized type or a raw type, then T must implement S, or a compile-time error occurs.