Reputation: 139
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
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