If Java String extends Object then why cant it be passed to <? extends Object> type
Asked Answered
T

2

6

For example why doesn't the following work?

Map<String, ? extends Object> table = new HashMap<>();
table.put("K", "V"); //Error V cannot be applied to ? extends String.

However String must extend Object, why does the above throw compiler error?

The IntelliJ error I get is

Wrong 2nd Argument Type. Found 'java.lang.String'required: '? extends java.lang.Object'

However the following works:

Map<String, ? super Object> table = new HashMap<>();
table.put("K", "V"); //Error V cannot be applied to ? extends String.

Now the above is truly weird. How can a lowerbound work on Object class?

I mean doesn't

? super Object

mean an "Unknown that is superclass of Object"?

AFAIK Object is at root of Java class hierarchy.

Tutankhamen answered 25/4, 2017 at 18:28 Comment(4)
Sorry that was a typo...Tutankhamen
See if this answers your question: https://mcmap.net/q/15314/-what-is-pecs-producer-extends-consumer-super/2891664Alundum
@Alundum I have done extensive research on Java wildcards and upper/lower bounds including ? super lower and ? extends Upper. This one seems strange.Tutankhamen
Possible duplicate of How can I add to List<? extends Number> data structures?Mentalist
C
10

Because ? extends Object does not mean "any type that extends Object." It means "some specific type, which we don't know what it is, as long as it extends Object." The difference is subtle, but significant!

The following code compiles totally fine:

Map<String, Integer> stringToInteger = new HashMap<>();
Map<String, ? extends Object> stringToWildcard = stringToInteger;

It makes sense that that would compile. stringToWildcard is a map whose value is "some type ... as long as it extends Object" -- and Integer is a type that extends object.

So, given that, imagine if your table.put("K", "V") worked. We could do this:

Map<String, Integer> stringToInteger = new HashMap<>();

Map<String, ? extends Object> stringToWildcard = stringToInteger;
stringToWildcard.put("K", "V");

Integer value = stringToInteger.get("K");

This would result in a ClassCastException on the last line, since the string "V" can't be cast to an Integer.

Instead, the compiler will disallow table.put("K", "V"). What it's telling you is: "hey, the value needs to be some specific type. I don't know what that type is, other than that it extends Object. But I can't let you put a String in, because I don't know if that type is String."

Creedon answered 25/4, 2017 at 18:41 Comment(8)
Very good explanation indeed! Thanks for your response. But why does the following work if table is declared as Map<String, ? super Object>. In this case table.put("K", "V"); is perfectly legit.Tutankhamen
@Tutankhamen Ah, that's because there you're saying "some specific type, which we don't know what it is, as long as it's a superclass of Object." And while we don't know what that type is, we do know that String matches that type. So in the first place, the compiler is telling you "there's no way of knowing if the actual type is a String, all we know is that it extends Object." In the second place, the compiler is telling you "we know the actual type is a superclass of Object, and we know that every class that's as superclass of Object is also a superclass of String."Creedon
While I continue to meditate over your explanation, the only thing that makes sense so far is compiler error on put(). I can see the problem the compiler is trying to prevent, but can't see why the ambiguity that applies to "? extends Object" does not apply to "? super Object". I continue to think over this.Tutankhamen
Anything that's a supertype of Object is also a supertype of String. Anything that's a subtype of Object is not necessarily a supertype of String. Objects can be cast only to their supertypes.Icaria
@Creedon Consider this counter example where SUV extends Car extends Vehicle. Then consider this Map<String, ? super SUV> stringToWildcard = new HashMap<>(). Map<String, Car> car. Then stringToWildcard = car works right? Then what if you do stringToWildCard.put("K", new Vehicle()); This seem to violate inheritance?Tutankhamen
@LouisWasserman Thanks but String is not a superclass of Object. So how come I can put it to Map<String, ? super Object> ?Tutankhamen
@LouisWasserman From What I gather, the wildcard applies to the entire Map entity and it doesnt have much context with respect to value value type alone. That's where the confusion is coming from.Tutankhamen
@Tutankhamen you've got it backwards. The Map's value type is some supertype of Object, so you can put a String into it, because the String is also an Object. String is a subclass of Object, and a subclass is what is needed.Icaria
T
0

This item solved my question:

How can I add to List<? extends Number> data structures?

The only problem I have in the above explanation is for a construct of form

List<? super T> myList;

the post goes on to say you can add any type T to myList or any of T's Supertype instance. That does not seem accurate. It looks like you can only add a T or any of its subtypes since a subtype of T is automatically subtype of any of T's supertypes. So a little ambiguity there.

Tutankhamen answered 26/4, 2017 at 14:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.