2

Excuse me if this was asked before (I'm almost sure) but I can't find the right keywords or dig it up from the pile of unrelated generics questions.

In short

This works:

Object      a = null;
Set<Object> b = (Set<Object>)a;

so why does this give a compiler error?

Set<Object>      c = null;
Set<Set<Object>> d = (Set<Set<Object>>)c;

and why is this simple workaround valid?

Set<Object>      e = null;
Set<?>           f = (Set<?>)e;
Set<Set<Object>> g = (Set<Set<Object>>)f;

Long version: (kept for legacy but no longer relevant to the question)

I have a library method that returns a Map<Number, Object> of a preprocessed JSON message. After manual deep-type-checking, I need to cast and return it as Map<Number, Map<String, Map<String, Object>>>, expecting an unchecked cast warning which I can ignore because I've just checked it.

However I'm getting the compiler error:

Error:(61, 10) java: Cannot cast from java.util.Map<java.lang.Number,java.lang.Object> to java.util.Map<java.lang.Number,java.util.Map<java.lang.String,java.util.Map<java.lang.String,java.lang.Object>>>

or in a more readable form (based on the error in the IDE):

Inconvertible types; cannot cast 'Map<Number,Object>' to 'Map<Number,Map<String,Map<String,Object>>>')

[edit] Changed from a how-to question to a why-question.

Mark Jeronimus
  • 9,278
  • 3
  • 37
  • 50
  • `?` is an unbounded wildcard, while `Object` is bounded. This may help: https://stackoverflow.com/questions/678822/what-is-the-difference-between-and-object-in-java-generics/678842 – Thomas Timbul Dec 03 '19 at 14:07
  • I think the answer has to do with type erasure. – Michele Dorigatti Dec 03 '19 at 14:08
  • Does this answer your question? [Is List a subclass of List? Why are Java generics not implicitly polymorphic?](https://stackoverflow.com/questions/2745265/is-listdog-a-subclass-of-listanimal-why-are-java-generics-not-implicitly-po) – Michele Dorigatti Dec 03 '19 at 16:40

2 Answers2

1

Note that since generic types are erased at runtime, nothing substantial actually happens when you do these casts. It's really all boils down to "does the compiler allow this?"

One of the aims of generics is to ensure type safety. If you could, for some reason, assign List<List<Object>> to List<Object>, you'd lose type safety:

List<List<Object>> listOfLists = new ArrayList<>();
List<Object> list = listOfLists; // suppose this is valid
list.add(new Object());
listOfLists.get(0); // what happens here?

(I am using lists here for convenience. You can easily apply this to maps or sets or any other generic type)

listOfLists.get(0) would return an instance of Object, as that is what we added, but it wouldn't! Because its type is List<List<Object>>! This is why being able to cast like this undermines type safety.

However, the two casts below are valid:

List<Object>      e = null;
List<?>           f = (List<?>)e;
List<List<Object>> g = (List<List<Object>>)f;

List<?> f means "f is a List of some class that extends Object, but that class could be anything". Note that List<?> is different from List<Object>. You can't do f.add("String") because ? is not necessarily a String, is it?

The first cast is allowed for the same reason why most people (incorrectly) believes that List<String> can be assigned to List<Object>. In fact, this cast is not needed. A List<String> is a List of stuff that extends Object.

The second cast is you telling the compiler that you know what the ? is, and it is List<Object>. The compiler warns you that you might be wrong about what ? is by issuing an unchecked warning.

In a sense, you are kind of asking why you can't cast directly from Integer to String, but can first cast from Integer to Object then to String.

I feel like your confusion might be caused by a misunderstanding of the differences between <Object> and <?>. This post might be worth reading.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • My confusion is probably caused by my perpetual confusion about the PECS principle, of which your "You can't do `f.add("String")`" is a nice example. Nice revealing explanation. – Mark Jeronimus Dec 03 '19 at 14:45
0

It's because of generics does not support sub-typing. Following code also does not compile.

List<Number> numbers = new ArrayList<Integer>();

Althought Number is super type of Integer, ArrayList<Number> is not super type of ArrayList<Integer>. These are completely different types.

In your case you are trying to sub type Object to Set<Object>

savas
  • 241
  • 2
  • 9