4

I expected that any type can be used instead of wildcard (?).

It is true with extends (as I expected), but it is a compile error with super (I don't understand why it is different from extends example).

How shall I think about this difference - is this a bug or a feature?

class Why {
    
    void fSuper(List<? super Map<String,?>> lst) {  }
    
    void fExtends(List<? extends Map<String,?>> lst) {  }

    void test(){
        
          // Why any type instead of ? gives error        
          fSuper(new ArrayList<Map<String, String>>()); // compile ERR - WHY ???
          fSuper(new ArrayList<Map<String, Byte>>());  // c. ERR - WHY ???
          fSuper(new ArrayList<Map<String, Map<Short, Boolean>>>()); // c. ERR - WHY ???         
          fSuper(new ArrayList<Map<String,?>>()); // OK
        
          // Any type instead of ? can be used OK   
          fExtends(new ArrayList<Map<String, String>>()); // OK
          fExtends(new ArrayList<Map<String, Byte>>());  // OK
          fExtends(new ArrayList<Map<String, Map<Short, Boolean>>>()); // OK     
          fExtends(new ArrayList<Map<String,?>>()); // OK

This question was inspired by example in answer of pdem here

His example seems very inconsistent with my IDE

void populateList(List<? super Map<String,?>> list) {
    list.clear();
    Map<String, String>  map;
    map = new HashMap<String,String>();
    map.put("key", "value"); // compiles
    // Map<String, String> for List<? super Map<String,?>>
    list.add(map);      // compiles !!
}

P.S. I understand PECS (Producer extends / Consumer super). (Please don't close the question for that reason). But I don't see how to use it in my first code snippet. I think I fully understand the second code snippet. But I don't understand why ArrayList<Map<String, String>> does not compile in my first code snippet (with ...? super) and does compile in the second code snippet and does compile in my first code snippet (with ...? extends).

I also realize that using wildcards (?) is often a way to trouble (especially if violating PECS prescriptions).

But I want to deeply understand generics, and the first code snippet beats my understanding so far.

Code Complete
  • 2,664
  • 1
  • 10
  • 28
  • Is `Map` a supertype or a subtype of `Map`? It can't be both. The only type that is both a supertype and a subtype of a type T is T itself. Every other type is one or the other (or neither). – John Kugelman Oct 22 '20 at 16:03
  • 3
    Did you read here [Difference between super T> and extends T> in Java](https://stackoverflow.com/questions/4343202/difference-between-super-t-and-extends-t-in-java) ? – Eklavya Oct 22 '20 at 16:06
  • 3
    Does this answer your question? [What is PECS (Producer Extends Consumer Super)?](https://stackoverflow.com/questions/2723397/what-is-pecs-producer-extends-consumer-super) – takendarkk Oct 22 '20 at 16:07
  • 1
    i suggest reading https://stackoverflow.com/questions/3546745/multiple-wildcards-on-a-generic-methods-makes-java-compiler-and-me-very-confu – Nathan Hughes Oct 22 '20 at 16:56
  • did any of the answers help you? – Eugene Nov 02 '20 at 16:39

2 Answers2

0

Based on the post suggested by @Nathan Hughes, I found this excellent article.

A wildcard instantiation with a lower bound is supertype of all instantiations of the generic type where the type argument is a supertype of the lower bound.

In the current case, for List<? super <Map<String, ?>>, we can only assign types with type parameter that are super type of Map<String, ?>.

In general, for lower bound wildcard, we should be able to assign the type parameters in the reverse direction, (i.e., in a statement the type parameter in the LHS must be assignable to the type parameter in the RHS).

In the below code, we cannot assign the first 3 values to the type parameters. So neither can a subtype with this type parameter can be assigned to the list.

HashMap m = new HashMap();
Map<String, String> m1 = (Map<String, ?>)(m); //error
Map<String, Byte> m2 = (Map<String, ?>)(m); //error
Map<String, Map<Short, Boolean>> m3 = (Map<String, ?>)(m); //error
Map<String, ?> m4 = (Map<String, ?>)(m); // ok
Prasanna
  • 2,195
  • 8
  • 11
  • or even [easier](https://stackoverflow.com/questions/3847162/java-generics-super-keyword/64490493#64490493) – Eugene Oct 23 '20 at 03:35
0

This is not going to easy, nor short; but in essence it's not that complicated.

What you need to understand first is that generics are invariant, you can read more here or a much longer read here and the most recent answer I've made here. In rather simple words:

Animal a = ...
Dog d = ...
a = d;

The assignment a = d works because Dog is an Animal; but at the same time:

List<Animal> a = new ArrayList<>();
List<Dog> d = new ArrayList<>();
a = d; 

this will not work, because generics are invariant (the links above explain why this is so).

The easy part here is that a bounded type in the form of ? extends ... is said to "make the type covariant", that is :

 List<? extends Animal> animals = new ArrayList<>();
 animals = d;

will work (as opposed to the example above a = d).

What if we want a List of List of Dogs? The naive approach says to try something like this:

 List<List<Dog>> dogs = new ArrayList<>();
 List<List<Animal>> animals = new ArrayList<>();
 animals = dogs;

which will fail, because we know already that List<Animal> can not be assigned to List<Dog> (in other words List<Dog> is not a subtype of List<Animal>). How about that ? extends...?

 List<List<Dog>> dogs = new ArrayList<>();
 List<? extends List<Animal>> animals = new ArrayList<>();
 animals = dogs;

still no. To make the type assignable, you would need:

 List<? extends List<? extends Animal>> animals = new ArrayList<>();
 animals = dogs; // now it works

You can also make it work via:

 List<? extends List<?>> animals = new ArrayList<>();

because ? is the super-type of all types.


We need this rather long intro because you are dealing with nested generics too. All the examples you have shown with fExtends(...) can be explained because:

  • bounded types (? extends...) makes the type covariant (assignable)

  • Map<String, String> or Map<String, Byte> or Map<String, Map<Short, Boolean>> or Map<String, ?> are all subtypes (or self) of : Map<String,?>.

For example all of these will compile:

 Map<String,?> one = new HashMap<>();
 Map<String, String> two = new HashMap<>();
 Map<String, Byte> three = new HashMap<>();
 // ... and so on for all the cases you have
 one = two;
 one = three;

The examples with fSuper are somehow more involved, but not really that complicated. Let's first look at the argument that it takes: List<? super Map<String,?>> : a list of an unknown type X, where X has to be a supertype (or self) of Map<String,?>. And this is why this :

fSuper(new ArrayList<Map<String, ?>>());

works: because you have passed a self type argument : Map<String, ?> (And ArrayList is-a List). I think this is rather easy for you to grasp.

This one:

fSuper(new ArrayList<Map<String, Map<Short, Boolean>>>())

does not compile because you need to pass a supertype of Map<String,?> and is this such a case? Let's illustrate:

method-argument:    Map < String,            ?          >
passed-value   :    Map < String,  Map<Short, Boolean>  >

Is Map<Short, Boolean> a supertype of ?. No, it is a subtype, thus it fails.

In the same course:

  • is Byte a supertype of ?

  • is String a supertype of ?

that is why those methods fail, too.

Eugene
  • 102,901
  • 10
  • 149
  • 252