2

I have a List of data objects which I need to group in various ways, then perform common operations on the grouped results. So I am trying to extract the common operations to a single method, as in the following contrived example:

private static void print(List<Integer> data, 
                          Collector<Integer, ?, Map<?, List<Integer>>> collector) {
    data.stream().collect(collector)
            .forEach((key, list) -> System.out.println(key + ": " + list));
}

private static void printByMagnitude() {
    List<Integer> data = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
    print(data, Collectors.<Integer,String>groupingBy(i -> i < 5 ? "small" : "large"));
}

private static void printByModulus() {
    List<Integer> data = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
    print(data, Collectors.<Integer,Integer>groupingBy(i -> i % 2));
}

Note that in the print method, the collector's result type is a Map with unknown keys, Map<?, List<Integer>>, since when I print by magnitude I use String keys, and when I print by modulus I use Integer keys.

This code gives two compilation errors, on both calls to Collectors.groupingBy. The first complains about the call with String keys:

Error:(19, 58) java: incompatible types:
java.util.stream.Collector<java.lang.Integer,capture#1 of ?,
    java.util.Map<java.lang.String,java.util.List<java.lang.Integer>>>
cannot be converted to
java.util.stream.Collector<java.lang.Integer,?,
    java.util.Map<?,java.util.List<java.lang.Integer>>>

The second complains about the call with Integer keys:

Error:(24, 59) java: incompatible types:     
java.util.stream.Collector<java.lang.Integer,capture#2 of ?,     
    java.util.Map<java.lang.Integer,java.util.List<java.lang.Integer>>>
cannot be converted to 
java.util.stream.Collector<java.lang.Integer,?,
    java.util.Map<?,java.util.List<java.lang.Integer>>>

The return type of Collectors.groupingBy is <T, K> Collector<T, ?, Map<K, List<T>>>, so in the Magnitude and Modulus cases this should be

Collector<Integer, ?, Map<String,List<Integer>>>
Collector<Integer, ?, Map<Integer,List<Integer>>>

respectively.

Why don't these match the collector parameter in print,

Collector<Integer, ?, Map<?, List<Integer>>> collector

?

Riaz
  • 874
  • 6
  • 15

3 Answers3

1

To explain with a simpler example, if you have a method foo(Number), you can pass in an Integer, because Integer is a subtype of Number. However, if you have a method foo(List<Number>), you can’t pass in a List<Integer>, because List<Integer> is not a subtype of List<Number>.

But if your method foo only wants to retrieve Numbers from the List, you might change the signature to foo(List<? extends Number>) (see also “What is PECS”), to allow lists being parametrized with a subtype of Number. List<Integer> is a subtype of List<? extends Number>

Going to the more complex, both, Map<Integer,List<Integer>> and Map<String,List<Integer>>, are a subtypes of Map<?,List<Integer>>, but Collector< … Map<Integer,List<Integer>> > and Collector< … Map<String,List<Integer>> > are not subtypes of Collector< … Map<?,List<Integer>> >.

The solution is the same. You want to retrieve the map from the Collector, so you have to resort to “? extends …”, i.e. use the type Collector<Integer, ?, ? extends Map<?, List<Integer>>>:

private static void print(List<Integer> data, 
                      Collector<Integer, ?, ? extends Map<?, List<Integer>>> collector) {
    data.stream().collect(collector)
            .forEach((key, list) -> System.out.println(key + ": " + list));
}

private static void printByMagnitude() {
    List<Integer> data = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
    print(data, Collectors.<Integer,String>groupingBy(i -> i < 5 ? "small" : "large"));
}

private static void printByModulus() {
    List<Integer> data = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
    print(data, Collectors.<Integer,Integer>groupingBy(i -> i % 2));
}
Community
  • 1
  • 1
Holger
  • 285,553
  • 42
  • 434
  • 765
0

Just simply write:

private static <T, U> void print(List<T> data, 
        Collector<T, ?, Map<U, List<T>>> collector) {
    data.stream().collect(collector)
    .forEach((key, list) -> System.out.println(key + ": " + list));
}

private static void printByMagnitude() {
    List<Integer> data = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
    print(data, Collectors.<Integer, String>groupingBy(i -> i < 5 ? "small" : "large"));
}

private static void printByModulus() {
    List<Integer> data = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
    print(data, Collectors.<Integer, Integer>groupingBy(i -> i % 2));
}

The difference here is that I replaced the unbounded wildcard type (the '?' character) with generic parameters (I made the print method generic). The big difference is that the '?' unbounded wildcard type stands for one type of something not any type of something. Since you had two types of something (Integer and String), the compiler was complaining.

Also note that you don't really need to call the generic method with parameters (as of Java 7 I think). You can also simply write:

private static <T, U> void print(List<T> data, 
        Collector<T, ?, Map<U, List<T>>> collector) {
    data.stream().collect(collector)
    .forEach((key, list) -> System.out.println(key + ": " + list));
}

private static void printByMagnitude() {
    List<Integer> data = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
    print(data, Collectors.groupingBy(i -> i < 5 ? "small" : "large"));
}

private static void printByModulus() {
    List<Integer> data = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
    print(data, Collectors.groupingBy(i -> i % 2));
}
Alma Alma
  • 1,641
  • 2
  • 16
  • 19
  • Thanks for the answer; making `print` a generic method does do the trick, but I was looking for a reason why the parameter wasn't matched as-is. See the accepted answer for a solution. Btw, you are correct that it is unnecessary to explicitly include the generic types in the method call. Doing this only helped clue the compiler into matching the `print` method. – Riaz Jun 29 '16 at 14:14
  • Thanks for the comment. But I can't see how is that solution better? I gave you the answer why it isn't matched. Because '?' stands for one type of something, and you provide two types. That's the simplest answer you will ever get. Anyway, it's your choice. Happy coding! – Alma Alma Jun 29 '16 at 19:26
  • Perhaps it's the wording, but your answer sounds as if only one of `String` or `Integer` should match `?`, which isn't true. The crux of my problem was in Collector's nested generic type. E.g. `List` and `List` both match `List>`, but neither `List>` nor `List>` match `List>`. Those would only match `List extends List>>`. – Riaz Jun 29 '16 at 22:20
  • No worries! I understand why you chose it. And that answer is a good answer also. And happy coding anyway! :) – Alma Alma Jun 29 '16 at 22:23
0

Simply replace

private static <A, B> void print(List<Integer> data, Collector<Integer, ?, Map<A, List<B>>> groupingBy) {
        data.stream().collect(groupingBy).forEach((key, value) -> System.out.println(key + " : " + value));
    }

with

private static void print(List<Integer> data, 
                          Collector<Integer, ?, Map<?, List<Integer>>> collector) {
    data.stream().collect(collector)
            .forEach((key, list) -> System.out.println(key + ": " + list));
}

why?

Because ? holds true only for Object Class ONLY and not even for its child classes

ie

AnyType<?> object=new AnyType<Object> // true
AnyType<?> object=new AnyType<Number> // since Number is subchild of Object but Still false

EDIT :

In your method printByModulus() you are passing Collectors.<Integer, Integer> while in method printByMagnitude() you are passing Collectors.<Integer, String> is the only reason for your error.

bananas
  • 1,176
  • 2
  • 19
  • 39
  • Thanks for the answer; making print a generic method does do the trick, but I was looking for a reason why the parameter wasn't matched as-is. See the accepted answer for a solution. – Riaz Jun 29 '16 at 14:15
  • yes you are right but I wanted to draw your attention to point that `?` stands for `Object` class only when you use without `super` or `extends` – bananas Jun 30 '16 at 04:04
  • Not quite; try compiling your own example; `List> object = new ArrayList()` will work fine, because `Number` matches the wildcard `?`. – Riaz Jun 30 '16 at 15:18