Simon Perkins
Simon Perkins

Reputation: 139

Compilation failure calling method with nested wildcard

I am having an issue with generics in java, specifically with wildcards. I have a Map (outerMap), whose values are another Map (innerMap) elements, whose values are List elements.

See the following java class, which I have created to demonstrate this behaviour. Import statements are not included. Ignore the use of streams inside the methods, that is irrelevant:

public class SSCCE {
  public static void main(String[] args) {
    // Setup
    List<Double> doubles = Arrays.asList(1.0,2.0,3.0);
    Map<Integer, List<Double>> innerMap = new HashMap<>();
    innerMap.put(1, doubles);
    Map<String, Map<Integer, List<Double>>> outerMap = new HashMap<>();
    outerMap.put("hello", innerMap);

    // Test    
    // This method call works fine. 
    Stream<Integer> streamFromInner = getStreamOfMappingToN(innerMap);

    // This method call does not work - causes below compilation error.
    Stream<Integer> stream = getStreamOfMergedDistinctMappingToN(outerMap);
  }

  private static <T, U> Stream<T> getStreamOfMappingToN(Map<T, ? extends Collection<U>> map) {
    return map.entrySet().stream().map(q -> q.getKey());
  }

  private static <T, U> Stream<T> getStreamOfMergedDistinctMappingToN(Map<String, Map<T, ? extends Collection<U>>> map) {
    return map.entrySet().stream().flatMap(
            p -> getStreamOfMappingToN(p.getValue())
    ).distinct();
  }
}

I am seeing the following compilation error, on the second method call:

method getStreamOfMergedDistinctMappingToN in class SSCCE cannot be applied to given types;
  required: Map<String,Map<T,? extends Collection<U>>>
  found: Map<String,Map<Integer,List<Double>>>
  reason: cannot infer type-variable(s) T,U
    (argument mismatch; Map<String,Map<Integer,List<Double>>> cannot be converted to Map<String,Map<T,? extends Collection<U>>>)
  where T,U are type-variables:
    T extends Object declared in method <T,U>getStreamOfMergedDistinctMappingToN(Map<String,Map<T,? extends Collection<U>>>)
    U extends Object declared in method <T,U>getStreamOfMergedDistinctMappingToN(Map<String,Map<T,? extends Collection<U>>>)

Can anybody suggest why this happening with the second method call, and not with the first? Both method signatures contain Map<T, ? extends Collection<U>>, but only the second fails to match against the call. If I replace the problematic ? extends Collection<U> with List<U>, it works fine because I am actually passing it a List, but that seems lazy and does not explain why I am seeing this error.

I have done a bit of reading around on this, some people have had similar problems and have had their problems explained - many remark that the level of the wildcard matters - but without an understanding of what is going on here it is hard for me to relate other solutions to this problem. It is still quite confusing. Through reading I have found that if I replace Map<T, ? extends Collection<U>> with ? extends Map<T, ? extends Collection<U>> in the second method declaration, it compiles fine, but I don't understand why, and then why the first method signature would work.

I would appreciate your feedback on this.

Upvotes: 2

Views: 98

Answers (1)

Tunaki
Tunaki

Reputation: 137289

You should change your methods to the following:

private static <T, U extends Collection<?>> Stream<T> getStreamOfMappingToN(Map<T, U> map) {
    return map.entrySet().stream().map(q -> q.getKey());
}

private static <T, U extends Collection<?>> Stream<T> getStreamOfMergedDistinctMappingToN(Map<String, Map<T, U>> map) {
    return map.entrySet().stream()
            .flatMap(p -> getStreamOfMappingToN(p.getValue())).distinct();
}

What changed is that instead of declaring the input type as ? extends Collection<U>, the method is accepting Map<T, U> but U is constrained to U extends Collection<?>.

This change represents what you really want to do: you are giving a Map as input. This map has two types T and U. T can be whatever type we give but U really needs to be a Collection of something, i.e. U must extends Collection<?>.


To find out why your example doesn't compile, please refer to this answer (or this one): wildcards only works on the 1st level, not deeper. As you found out, you need to add ? extends Map<...> to counter the fact that the wildcard was not applied recursively.

Still, I would recommend you use the first solution as I feel it gives cleaner code and the proper intent, instead of messing around with wildcards.

Upvotes: 1

Related Questions