Java 8流 - 对元组流进行分组

8

编辑:我正在重新表述问题,以使其更清晰

我写了这段代码

    List<ImmutablePair<Integer, Integer>> list = new ArrayList<ImmutablePair<Integer, Integer>>();
    list.add(new ImmutablePair(1, 1));
    list.add(new ImmutablePair(1, 1));
    list.add(new ImmutablePair(1, 1));
    list.add(new ImmutablePair(2, 2));
    list.add(new ImmutablePair(2, 2));
    list.add(new ImmutablePair(2, 2));
    list.add(new ImmutablePair(3, 3));
    list.add(new ImmutablePair(3, 3));
    list.add(new ImmutablePair(3, 3));
    Stream<ImmutablePair<Integer, Integer>> stream = list.stream();
    Map<Integer, Integer> result = stream.collect(Collectors.groupingBy(
       ImmutablePair::getLeft,
            Collectors.mapping(
                    ImmutablePair::getRight,
                    Collectors.summingInt(Comparator.comparing(ImmutablePair::getRight))
            )                
    ));

我希望使用流处理这个列表,以便输出一个包含键(1, 3), (2, 6), (3, 9)的映射。
所以基本上,我们按元组左侧项进行分组,然后对右侧项求和。
这段代码无法编译,编译器说它无法解析getLeft、getRight方法。
以下是编译器的错误消息。
/Users/abhi/JavaProjects/MovieLambda2/src/main/java/com/abhi/MovieLambda2.java:229: error: method summingInt in class Collectors cannot be applied to given types;
                            Collectors.summingInt(Comparator.comparing(ImmutablePair::getRight))
                                      ^
  required: ToIntFunction<? super T#1>
  found: Comparator<Object>
  reason: no instance(s) of type variable(s) T#2,U exist so that Comparator<T#2> conforms to ToIntFunction<? super T#1>
  where T#1,T#2,U are type-variables:
    T#1 extends Object declared in method <T#1>summingInt(ToIntFunction<? super T#1>)
    T#2 extends Object declared in method <T#2,U>comparing(Function<? super T#2,? extends U>)
    U extends Comparable<? super U> declared in method <T#2,U>comparing(Function<? super T#2,? extends U>)
/Users/abhi/JavaProjects/MovieLambda2/src/main/java/com/abhi/MovieLambda2.java:229: error: incompatible types: cannot infer type-variable(s) T,U
                            Collectors.summingInt(Comparator.comparing(ImmutablePair::getRight))
                                                                      ^
    (argument mismatch; invalid method reference
      no suitable method found for getRight(Object)
          method Pair.getRight() is not applicable
            (actual and formal argument lists differ in length)
          method ImmutablePair.getRight() is not applicable
            (actual and formal argument lists differ in length))
  where T,U are type-variables:
    T extends Object declared in method <T,U>comparing(Function<? super T,? extends U>)
    U extends Comparable<? super U> declared in method <T,U>comparing(Function<? super T,? extends U>)
/Users/abhi/JavaProjects/MovieLambda2/src/main/java/com/abhi/MovieLambda2.java:229: error: invalid method reference
                            Collectors.summingInt(Comparator.comparing(ImmutablePair::getRight))
                                                                       ^
  non-static method getRight() cannot be referenced from a static context
  where R is a type-variable:
    R extends Object declared in class ImmutablePair
/Users/abhi/JavaProjects/MovieLambda2/src/main/java/com/abhi/MovieLambda2.java:228: error: invalid method reference
                            ImmutablePair::getRight,
                            ^
  non-static method getRight() cannot be referenced from a static context
  where R is a type-variable:
    R extends Object declared in class ImmutablePair
/Users/abhi/JavaProjects/MovieLambda2/src/main/java/com/abhi/MovieLambda2.java:226: error: invalid method reference
                    ImmutablePair::getLeft,
                    ^
  non-static method getLeft() cannot be referenced from a static context
  where L is a type-variable:
    L extends Object declared in class ImmutablePair

3
请提供一个简单的示例,以便我们更好地理解问题并进行复现。否则,我们很难重现该问题。 - Tunaki
1
Collectors.groupBy(ImmutablePair::getLeft) 单独使用呢?会失败吗? - Lucia Pasarin
2
你试图使用Movie的方法引用来对ImmutablePair的实例进行分组,这是不可能的。这就像按香蕉的颜色对人进行排序一样荒谬。你可以按照人的身高、体重或年龄进行排序,但不能按香蕉的属性进行排序。如果你发布了确切的错误消息而不是解释它并给出自己错误的理解,我们可以解释它的确切含义。如果你发布涉及的类,我们可以帮助你修复代码。 - JB Nizet
我已重新表述了我的问题并简化了示例。现在应该很清楚了。 - Knows Not Much
1
你为什么要使用 Comparator.comparing()?从你的新示例中,我无法理解你正在比较什么以及为什么需要这种比较来对每个组的值进行求和。 - fps
1
现在这是完全不同的问题... - Tagir Valeev
2个回答

6
假设您的ImmutablePair类看起来像这样(根据示例代码是否在主方法中执行,此类可能需要是静态的):
class ImmutablePair<T,T2> {
    private final T left;
    private final T2 right;

    public ImmutablePair(T left, T2 right) {
        this.left = left;
        this.right = right;

    }

    public T getLeft() {
        return left;
    }

    public T2 getRight() {
        return right;
    }
}

这对我来说似乎有效:

List<ImmutablePair<Integer, Integer>> list = new ArrayList<>();
    list.add(new ImmutablePair(1, 1));
    list.add(new ImmutablePair(1, 1));
    list.add(new ImmutablePair(1, 1));
    list.add(new ImmutablePair(2, 2));
    list.add(new ImmutablePair(2, 2));
    list.add(new ImmutablePair(2, 2));
    list.add(new ImmutablePair(3, 3));
    list.add(new ImmutablePair(3, 3));
    list.add(new ImmutablePair(3, 3));
    Map<Integer, Integer> collect = list.stream()
        .collect(
            Collectors.groupingBy(
                ImmutablePair::getLeft,
                Collectors.summingInt(ImmutablePair::getRight)));

    System.out.println(collect);

结果如下:

{1=3, 2=6, 3=9}

可能存在的问题

问题出现在这段代码中:

        Collectors.mapping(
                ImmutablePair::getRight,
                Collectors.summingInt(Comparator.comparing(ImmutablePair::getRight))
        )

Collectors.mapping收集器首先将T类型对象转换为U类型对象,然后在U类型上运行一个收集器。(这是我的javadoc要点)
所以你正在做的事情类似于:
- 将ImmutablePair转换为Integer。 - 第二个参数现在是一个期望Integer类型的收集器,但你正在“使用ImmutablePair类型”。此外,Comparator.compare()返回一个Comparator,而你想返回一个Integer类型。因此,要修复你的代码行,需要将其更改为以下内容:
Collectors.mapping(ImmutablePair::getRight, Collectors.summingInt(Integer::valueOf))
但你不需要这样做,因为你可以直接调用Collectors.summingInt来简化它:

Collectors.summingInt(ImmutablePair::getRight)


4

更新: 这回答了一个原始问题(第2版)。@Shiraaz.M已经很好地回答了新问题。


您的 Comparator 接受一对值,因此您需要使用 Collectors.mapping 调用 getRight

Map<String, Optional<Movie>> map = ml.getDataAsStream()
    .<ImmutablePair<String, Movie>>flatMap(x -> x.getGenre()
            .map(g -> new ImmutablePair<String, Movie>(g, x)))
    .collect(
            Collectors.groupingBy(
                ImmutablePair::getLeft,
                Collectors.mapping(ImmutablePair::getRight, 
                    Collectors.maxBy(Comparator.comparing(Movie::getVoteCount)))
            ));

请注意,取决于所使用的编译器,您可能需要显式指定某些类型,因为自动推断可能会失败。
实际上,在我的 StreamEx 库中,这样的场景得到了很好的支持,它增强了标准的 Java 流。编写以下内容可以实现相同的结果:
Map<String, Optional<Movie>> map = StreamEx.of(ml.getDataAsStream())
    .cross(Movie::getGenre) // create (Movie, Genre) entries
    .invert() // swap entries to get (Genre, Movie)
    .grouping(Collectors.maxBy(Comparator.comparing(Movie::getVoteCount)));

最后需要注意的是,使用maxBy收集器会得到Optional值,但这些值没有意义,因为它们不可能为空。对于maxBy/minBy情况,最好使用带有自定义合并函数的toMap收集器。
Map<String, Movie> map = ml.getDataAsStream()
    .<ImmutablePair<String, Movie>>flatMap(x -> x.getGenre()
            .map(g -> new ImmutablePair<String, Movie>(g, x)))
    .collect(Collectors.toMap(ImmutablePair::getLeft, ImmutablePair::getRight, 
            BinaryOperator.maxBy(Comparator.comparing(Movie::getVoteCount))));

或者使用StreamEx:

Map<String, Movie> map = StreamEx.of(ml.getDataAsStream())
    .cross(Movie::getGenre)
    .invert()
    .toMap(BinaryOperator.maxBy(Comparator.comparing(Movie::getVoteCount)));

我认为我的上一个例子有点复杂。所以我…… - Knows Not Much
这个不起作用。我实施了上面的建议,但我仍然收到编译器消息“无法解析getLeft”。 - Knows Not Much
2
有趣的是,invert() 函数是做什么用的? - John McClean
2
@JohnMcClean,在键值对中,交换键和值 - Tagir Valeev
非常感谢您,这是一个很棒的答案。抱歉更改了我的初始问题。许多人告诉我我的问题不清楚,所以我试图简化我的问题。 - Knows Not Much

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接