2

While there are similar questions around like this and this; those don't answer what are the difference between two ways of generic declaration and why one of it compiles but other not in the following example?
The one that doesn't compile was based on official tutorial as for me.

public void processMap1(Map<String, List<?>> map) {
}

public <T> void processMap2(Map<String, List<T>> map) {
}

public void f() {
    Map<String, List<String>> map = new HashMap<>();

    processMap1(map); // ERROR
    processMap2(map); // OK
}

I want my methods to work with both Map<String, List<String>> and Map<String, List<Object>>

Community
  • 1
  • 1
Mike
  • 20,010
  • 25
  • 97
  • 140
  • 2
    First one doesn't add any new information; all Java classes extend Object. The compiler is teaching you something valuable. – duffymo Jun 01 '16 at 12:36
  • And note: for processMap2 ... you also do not need the `extends Object` part. Besides: it is not really good style to just raise up the same question after it was "duplicated" just a few minutes ago. – GhostCat Jun 01 '16 at 12:37
  • Note: the first one compiles if you make the value type `? extends List extends Object>` (or `? extends List>`). – Andy Turner Jun 01 '16 at 12:37
  • 1
    http://stackoverflow.com/questions/8055389/whats-the-difference-between-and-extends-object-in-java-generics Here is a answer for your question.. – TYL Jun 01 '16 at 12:38
  • 1
    @TubaYlm not really. That just explains why `extends Object` is unnecessary. – Andy Turner Jun 01 '16 at 12:39
  • @duffymo, `void processMap1(Map> map)` gives the same compilation error when passing `Map>`. So what exactly compiler wants to teach me? – Mike Jun 01 '16 at 12:47
  • "not need the extends Object part" - updated my question – Mike Jun 01 '16 at 12:51
  • Use generics as best you can, but understand type erasure. That's the lesson. – duffymo Jun 01 '16 at 12:51
  • Both are generics, isn't it? – Mike Jun 01 '16 at 12:53
  • No, let it go. The compiler won't be denied. Your idea is a bad one. – duffymo Jun 01 '16 at 12:54
  • you should read about multi-level wildcards – qwerty Jun 01 '16 at 12:57
  • Take a look on this answer http://stackoverflow.com/a/18176765/1324287 – olexd Jun 01 '16 at 13:00
  • @duffymo Although there may be similar questions, and there may be questions that are different and nevertheless have *answers* that are similar to the answer for *this* question, I find some things questionable here. First of all, some of your comments (but I don't want to argue too much about that). But more importantly: The question that you have considered this a duplicate of is (to my understanding) **totally unrelated** to this one. – Marco13 Jun 01 '16 at 13:00
  • @duffymo, my idea was based on [official tutorial](https://docs.oracle.com/javase/tutorial/java/generics/upperBounded.html) and you say compiler don't like the idea. I'm asking why? – Mike Jun 01 '16 at 13:01
  • It's obvious - you got an error message from the compiler. End of story. Why? Because generics in Java weren't written to conform to your erroneous understanding. Time to move on and let it go. – duffymo Jun 01 '16 at 13:02
  • @qwerty, I have read [about multi-level wildcards](http://stackoverflow.com/questions/19219635/java-hashmap-nested-generics-with-wildcards) before my post, but I don't understand the difference between the two generic method declaration approaches and why one of it works but other not. – Mike Jun 01 '16 at 13:05

1 Answers1

3

You have a method that expects an object of type X. In order to pass an object to this method, this object must have a type Y that is a subtype of X.

This may be obvious for simple cases:

void processNumber(Number number) { ... }
void callIt()
{
    Integer integer = null;
    processNumber(integer); // Fine. Integer is a subtype of Number

    String string = null;
    processNumber(string); // Error. String is NOT a subtype of Number
}

So far, so good. But now you're entering the confusing world of subtype-relationships for parameterized types. Angelika Langer goes into the details in her Generics FAQ: Which super-subtype relationships exist among instantiations of generic types?:

This is fairly complicated and the type relationships are best determined setting up tables as explained in this FAQ entry.

So I won't even try to reproduce this here, but only try to explain (simplified) why the second version works, but the first one doesn't.


The fact that the second version works can be explained with the type inference that the compiler does. Basically, it just looks at the argument, sees that it is Map<String, List<String>>, and infers that the T of the method declaration must be String for this to work. You can imagine this as T simply being replaced with String, and then the method matches perfectly. (It's not really so easy, but can intuitively be understood like this)


The reason of why the first version does not work is seemingly simple as well. Coming back to the initial statement: The method expects an object that has a subtype of the type declared in the method signature. And the key point is:

Map<String, List<String>> is not a subtype of Map<String, List<?>>

This fact can (in this case) even be simplified by omitting the method invocation:

Map<String, List<String>> map = new HashMap<>();
Map<String, List<?>> otherMap = map; // Does not work

Again, I'll leave the details of the "type theory" behind that to the FAQ entry linked above, but to at least explain why it is not allowed: A Map<String, List<?>> is a map that maps strings to lists that contain an unknown/unspecified type. So the following implementation of your processMap1 method would be valid:

public void processMap1(Map<String, List<?>> map) 
{
    List<Number> numbers = new ArrayList<Number>();
    map.put("x", numbers);
}

And imagine, calling this method with a Map<String, List<String>> was valid:

public void f() {
    Map<String, List<String>> map = new HashMap<>();
    processMap1(map); // Imagine this was possible
    List<String> shouldBeStrings = map.get("x");
}

then you would end up with a ClassCastException sooner or later, because you pass Map<String, List<String>> to the method, but the method is allowed to put a List<Number> into this map.

Or for short: It is not allowed because it is not type-safe.


Edit in response to the comments (this goes somewhat beyond the original question, so I'll try to keep it short) :

For the second case, there is no T to capture any type. The type is ?, which, intuitively, can stand for any type. In the case of the suggested call from reverse to rev for lists, the T is still ?, but may be used to dedicatedly represent the type of the elements in the list. Or again, referring to the subtype relationships:

List<T> is always a subtype of List<?> (regardless of what T is)

The suggested call from processMap1 to processMap2 does not work for the reasons explained above: Even when Y is a subtype of X, the type Map<String, Y> is not a subtype of Map<String, X>. Here, this question may be relevant.

Community
  • 1
  • 1
Marco13
  • 53,703
  • 9
  • 80
  • 159
  • would you be able to explain another quirk btw? `public static void reverse(List> list) { rev(list); } private static void rev(List list) { }` works, but `public void processMap1(Map> map) { processMap2(map); //compile error } public void processMap2(Map> map) { }`will not!! Why? – senseiwu Jun 01 '16 at 13:30
  • In the fist example, with just List, T has captured the wildcard, but in second one it doesn't? – senseiwu Jun 01 '16 at 13:32
  • *Even when Y is a subtype of X, the type Map is not a subtype of Map* => I know this, but my question was how would the relationship between `List>` and `List` be any different from that of `Map>` and `Map>` .. Thanks!! – senseiwu Jun 01 '16 at 13:53
  • @zencv because it's the other way around. You are not trying to bind `List` to `List>` but `List>` to `List`. This means `?` is bound to `T`, which works fine. But you should ask this in your own separate question. – André Stannek Jun 01 '16 at 13:59
  • @Marco13, thanks for your answer! Your example clearly shows that wildcard doesn't imply any type binding with outer visibility context but only imply type casting of input parameter (and ability to generalize it to some extent). Type binding should be the point here which allow compile-time type checking. Also I found [this](http://stackoverflow.com/questions/18176594/when-to-use-generic-methods-and-when-to-use-wild-card) thread usefull. – Mike Jun 01 '16 at 14:16