29

In this example:

import java.util.*;

public class Example {
    static void doesntCompile(Map<Integer, List<? extends Number>> map) {}
    static <T extends Number> void compiles(Map<Integer, List<T>> map) {}

    static void function(List<? extends Number> outer)
    {
        doesntCompile(new HashMap<Integer, List<Integer>>());
        compiles(new HashMap<Integer, List<Integer>>());
    }
}

doesntCompile() fails to compile with:

Example.java:9: error: incompatible types: HashMap<Integer,List<Integer>> cannot be converted to Map<Integer,List<? extends Number>>
        doesntCompile(new HashMap<Integer, List<Integer>>());
                      ^

while compiles() is accepted by the compiler.

This answer explains that the only difference is that unlike <? ...>, <T ...> lets you reference the type later, which doesn't seem to be the case.

What is the difference between <? extends Number> and <T extends Number> in this case and why doesn't the first compile?

Andronicus
  • 25,419
  • 17
  • 47
  • 88
Dev Null
  • 4,731
  • 1
  • 30
  • 46
  • Comments are not for extended discussion; this conversation has been [moved to chat](https://chat.stackoverflow.com/rooms/209170/discussion-on-question-by-dev-null-what-is-the-difference-between-extends-bas). – Samuel Liew Mar 06 '20 at 15:28
  • [1] [Java Generic type : difference between List extends Number> and List ](https://stackoverflow.com/q/18187005/2985643) appears to be asking the same question, but while it may be of interest it is not really a duplicate. [2] While this is a good question, the title doesn't properly reflect the specific question being asked in the final sentence. – skomisa Mar 07 '20 at 00:39
  • This is already answered here [Java Generic List>](https://stackoverflow.com/questions/746089/java-generic-listlist-extends-number) – Mạnh Quyết Nguyễn Mar 08 '20 at 01:04
  • How about the explanation [here](https://stackoverflow.com/questions/18176594/when-to-use-generic-methods-and-when-to-use-wild-card)? – jrook Mar 08 '20 at 21:26

4 Answers4

13

By defining the method with the following signature:

static <T extends Number> void compiles(Map<Integer, List<T>> map) {}

and invoking it like:

compiles(new HashMap<Integer, List<Integer>>());

you're matching T against the type you're providing.

In the jls §8.1.2 we find, that (interesting part bolded by me):

A generic class declaration defines a set of parameterized types (§4.5), one for each possible invocation of the type parameter section by type arguments. All of these parameterized types share the same class at run time.

In other words, the type T is matched against the input type and assigned Integer. The signature will effectively become static void compiles(Map<Integer, List<Integer>> map).

When it comes to doesntCompile method, jls defines rules of subtyping (§4.5.1, bolded by me):

A type argument T1 is said to contain another type argument T2, written T2 <= T1, if the set of types denoted by T2 is provably a subset of the set of types denoted by T1 under the reflexive and transitive closure of the following rules (where <: denotes subtyping (§4.10)):

  • ? extends T <= ? extends S if T <: S

  • ? extends T <= ?

  • ? super T <= ? super S if S <: T

  • ? super T <= ?

  • ? super T <= ? extends Object

  • T <= T

  • T <= ? extends T

  • T <= ? super T

This means, that ? extends Number indeed contains Integer or even List<? extends Number> contains List<Integer>, but it's not the case for Map<Integer, List<? extends Number>> and Map<Integer, List<Integer>>. More on that topic can be found in this SO thread. You can still make the version with ? wildcard work by declaring, that you expect a subtype of List<? extends Number>:

public class Example {
    // now it compiles
    static void doesntCompile(Map<Integer, ? extends List<? extends Number>> map) {}
    static <T extends Number> void compiles(Map<Integer, List<T>> map) {}

    public static void main(String[] args) {
        doesntCompile(new HashMap<Integer, List<Integer>>());
        compiles(new HashMap<Integer, List<Integer>>());
    }
}
Andronicus
  • 25,419
  • 17
  • 47
  • 88
  • [1] I think you meant `? extends Number` rather than `? extends Numeric`. [2] Your assertion that _"it's not the case for List extends Number> and List"_ is incorrect. As @VinceEmigh has already pointed out, you can create a method `static void demo(List extends Number> lst) { }` and call it like this `demo(new ArrayList());` or this `demo(new ArrayList());`, and the code compiles and runs OK. Or am I perhaps misreading or misunderstanding what you stated? – skomisa Mar 09 '20 at 00:32
  • @You're right in both cases. When it comes to your second point, I wrote it in a misleading way. I meant `List extends Number>` as a type parameter of the whole map, not itself. Thank you very much for the comment. – Andronicus Mar 09 '20 at 04:53
  • @skomisa from the same reason `List` does not contain `List`. Suppose you have a function `static void check(List numbers) {}`. When invoking with `check(new ArrayList());` it does not compile, you have to define the method as `static void check(List extends Number> numbers) {}`. With the map it's the same but with more nesting. – Andronicus Mar 09 '20 at 06:16
  • 1
    @skomisa just as `Number` is a type parameter of list and you need to add `? extends` to make it covariant, `List extends Number>` is a type parameter of `Map` and also needs `? extends` for covariance. – Andronicus Mar 09 '20 at 06:18
  • 1
    OK. Since you provided the solution for a multi level wildcard (aka _"nested wildcard"_?), and linked to the relevant JLS reference, have the bounty. – skomisa Mar 11 '20 at 15:26
6

In the call:

compiles(new HashMap<Integer, List<Integer>>());

T is matched to Integer, so the type of the argument is a Map<Integer,List<Integer>>. It's not the case for the method doesntCompile: the type of the argument stays Map<Integer, List<? extends Number>> whatever the actual argument in the call; and that is not assignable from HashMap<Integer, List<Integer>>.

UPDATE

In the doesntCompile method, nothing prevents you to do something like this:

static void doesntCompile(Map<Integer, List<? extends Number>> map) {
    map.put(1, new ArrayList<Double>());
}

So obviously, it cannot accept a HashMap<Integer, List<Integer>> as the argument.

Andronicus
  • 25,419
  • 17
  • 47
  • 88
Maurice Perry
  • 9,261
  • 2
  • 12
  • 24
  • What would a valid call to `doesntCompile` be like, then? Just curious about that. – Aritz Mar 05 '20 at 07:50
  • 1
    @XtremeBiker `doesntCompile(new HashMap>());` would work, as would `doesntCompile(new HashMap<>());`. – skomisa Mar 05 '20 at 08:23
  • @XtremeBiker, even this would work, Map> map = new HashMap>(); map.put(null, new ArrayList()); doesntCompile(map); – MOnkey Mar 05 '20 at 08:38
  • "that is not assignable from `HashMap>`" could you please elaborate why it is not assignable from it? – Dev Null Mar 05 '20 at 22:35
  • What is the difference between this and `static void demo(List extends Number> list)`? You are not allowed to do `list.add(new Double(0.0))` inside it either, but it fails to compile inside `demo`, not outside like `doesntCompile`. – Dev Null Mar 06 '20 at 08:29
  • @DevNull In your `demo` example, `list` is a list of some unknown type extending `Number`, so you don't know that this unknown type is `Double`; hence the compilation error. – Maurice Perry Mar 06 '20 at 09:30
  • Sorry for not being clear enough: why in case 1 (in the update to this answer) a potential compilation error (`map.put(...)`) _inside of a method_ leads to a compilation error _outside_ of the method, while in case 2 (`demo`) it gets inside of the method and fails to compile _there_? – Dev Null Mar 06 '20 at 12:07
  • @MauricePerry sorry for the edit, I have mistakenly thought your answer was mine. Already reverted:) – Andronicus Mar 08 '20 at 15:27
2

Simplied example of demonstration. Same example can be visualize like below.

static void demo(List<Pair<? extends Number>> lst) {} // doesn't work
static void demo(List<? extends Pair<? extends Number>> lst) {} // works
demo(new ArrayList<Pair<Integer>()); // works
demo(new ArrayList<SubPair<Integer>()); // works for subtype too

public static class Pair<T> {}
public static class SubPair<T> extends Pair<T> {}

List<Pair<? extends Number>> is a multi-level wildcards type whereas List<? extends Number> is a standard wildcard type .

Valid concrete instantiations of the wild card type List<? extends Number> include Number and any subtypes of Number whereas in case of List<Pair<? extends Number>> which is a type argument of type argument and itself has a concrete instantiation of the generic type.

Generics are invariant so Pair<? extends Number> wild card type can only accept Pair<? extends Number>>. Inner type ? extends Number is already covariant. You have to make the enclosing type as covariant to allow covariance.

s7vr
  • 73,656
  • 11
  • 106
  • 127
  • How is it that `>` doesn't work with `>` but does work with ` >`? – jaco0646 Mar 12 '20 at 16:16
  • @jaco0646 You are essentially asking the same question as the OP, and [the answer from Andronicus](https://stackoverflow.com/a/60585268/2985643) has been accepted. See the code example in that answer. – skomisa Mar 15 '20 at 16:54
  • @skomisa, yes, I'm asking the same question for a couple of reasons: one is that this answer doesn't actually seem to address the OP's question; but two is that I find this answer easier to comprehend. I cannot follow the answer from Andronicus in any way that leads me to understand nested vs non-nested generics, or even `T` vs `?`. Part of the problem is that when Andronicus reaches the vital point of his explanation, he defers to another thread that uses only trivial examples. I was hoping to get a clearer and more complete answer here. – jaco0646 Mar 16 '20 at 04:09
  • 1
    @jaco0646 OK. The document _"Java Generics FAQs - Type Arguments"_ by Angelika Langer has an FAQ titled [What do multi-level (i.e., nested) wildcards mean?](http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeArguments.html#FAQ104). That is the best source I know of for explaining the issues raised in the OP's question. The rules for nested wildcards are neither straightforward nor intuitive. – skomisa Mar 16 '20 at 04:52
1

I'd recommend you to look in documentation of generic wildcards especially guidelines for wildcard use

Frankly speaking your method #doesntCompile

static void doesntCompile(Map<Integer, List<? extends Number>> map) {}

and call like

doesntCompile(new HashMap<Integer, List<Integer>>());

Is fundamentally incorrect

Let's add legal implementation:

    static void doesntCompile(Map<Integer, List<? extends Number>> map) {
        List<Double> list = new ArrayList<>();
        list.add(0.);
        map.put(0, list);
    }

It is really fine, because Double extends Number, so put List<Double> is absolutely fine as well as List<Integer>, right?

However, do you still suppose it's legal to pass here new HashMap<Integer, List<Integer>>() from your example?

Compiler does not think so, and is doing his (its?) best to avoid such situations.

Try to do the same implementation with method #compile and compiler will obviously does not allow you to put a list of doubles into map.

    static <T extends Number> void compiles(Map<Integer, List<T>> map) {
        List<Double> list = new ArrayList<>();
        list.add(10.);
        map.put(10, list); // does not compile
    }

Basically you can put nothing but List<T> that's why it's safe to call that method with new HashMap<Integer, List<Integer>>() or new HashMap<Integer, List<Double>>() or new HashMap<Integer, List<Long>>() or new HashMap<Integer, List<Number>>().

So in a nutshell, you are trying to cheat with compiler and it fairly defends against such cheating.

NB: answer posted by Maurice Perry is absolutely correct. I'm just not sure it's clear enough, so tried (really hope I managed to) to add more extensive post.

Makhno
  • 441
  • 2
  • 6