Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unexpected type safety violation

Tags:

java

casting

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?

like image 890
Marko Topolnik Avatar asked Jan 02 '15 19:01

Marko Topolnik


Video Answer


2 Answers

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.

like image 190
JB Nizet Avatar answered Sep 29 '22 08:09

JB Nizet


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.

like image 22
Makoto Avatar answered Sep 29 '22 08:09

Makoto