Java 8中的对象分组

5

我有如下内容:

public class MyClass {
private Long stackId
private Long questionId
}

这是一个包含100个元素的集合,其中stackid可能会重复,但questionIds不同。这是stackId和questionId之间的一对多关系。

是否有一种流式的Java 8方法可以将其转换为以下结构:

public class MyOtherClass {
private Long stackId
private Collection<Long> questionIds
}

这将是一个包含25个实例的集合,每个实例都有一个嵌套的包含4个questionIds的集合。

输入:

[{1,100},{1,101},{1,102},{1,103},{2,200},{2,201},{2,202},{1,203}]

输出

[{1, [100,101,102,103]},{2,[200,201,202,203]}]

2
将http://www.markhneedham.com/blog/2014/02/23/java-8-group-by-with-collections/中的内容先转换为`Map<Long, Collection<Long>>,然后将该映射的条目转换为(第二种类型的)MyClass`实例。 - Andy Turner
1
我仍然不理解“Which would be a collection of 25”的部分。你能详细说明一下吗?你是想让这个集合最多包含25个元素吗?那么剩下的元素该怎么处理呢? - Tunaki
@Tunaki 输出的根元素比输入的少,但每个根元素都与子元素(questionIds)具有一对多的关系。 - NimChimpsky
1
但是为什么是25呢?这个数字从哪里来?还有为什么是“一组大约100个”? - Tunaki
那没关系,数字只是为了突出减少的情况而选择的。它可以是一百万比一。 - NimChimpsky
3个回答

9

使用 Stream API 的简单方法包括 2 个 Stream 管道:

  • 第一个管道创建一个临时的 Map<Long, List<Long>>,将 stackId 映射到 questionIds。这是通过 groupingBy(classifier, downstream) 收集器实现的,我们按 stackId 进行分类,并且具有相同 stackId 的值被映射到它们的 questionId(使用 mapping)并收集成列表(使用 toList())。
  • 第二个管道将该映射中的每个条目转换为一个 MyOtherClass 实例,并将其收集到列表中。

假设您有一个构造函数 MyOtherClass(Long stackId, Collection<Long> questionIds),则示例代码如下:

Map<Long, List<Long>> map = 
    list.stream()
        .collect(Collectors.groupingBy(
            MyClass::getStackId,
            Collectors.mapping(MyClass::getQuestionId, Collectors.toList())
        ));

List<MyOtherClass> result = 
    map.entrySet()
       .stream()
       .map(e -> new MyOtherClass(e.getKey(), e.getValue()))
       .collect(Collectors.toList());

使用StreamEx库,您可以在单个Stream流水线中完成此操作。该库提供了一个pairingfirst收集器。这使得可以将两个收集器配对,并在两个收集结果上执行完成操作:

  • 第一个收集器仅保留分组后的元素的第一个stackId(按构造方式它们都相同)
  • 第二个收集器将每个元素映射到其questionId并将其收集到列表中。
  • 完成操作只需返回MyOtherClass的新实例。

示例代码:

import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.mapping;
import static java.util.stream.Collectors.toList;
import static one.util.streamex.MoreCollectors.first;
import static one.util.streamex.MoreCollectors.pairing;

// ...

Collection<MyOtherClass> result = 
    StreamEx.of(list)
            .groupingBy(
                MyClass::getStackId,
                pairing(
                    collectingAndThen(mapping(MyClass::getStackId, first()), Optional::get),
                    mapping(MyClass::getQuestionId, toList()),
                    MyOtherClass::new
                )
            ).values();

哇,感谢您详细的回答。您使用过其他函数库吗?https://github.com/akullpp/awesome-java#functional-programming。这个http://www.javaslang.io/看起来非常活跃/精致。 - NimChimpsky
1
@NimChimpsky 不,我从来没有用过那些(有点 jOOL)。 - Tunaki

2
List<MyClass> inputs = Arrays.asList(
    new MyClass(1L, 100L),
    new MyClass(1L, 101L),
    new MyClass(1L, 102L),
    new MyClass(1L, 103L),
    new MyClass(2L, 200L),
    new MyClass(2L, 201L),
    new MyClass(2L, 202L),
    new MyClass(2L, 203L)
);

Map<Long, List<Long>> result = inputs
    .stream()
    .collect(
      Collectors.groupingBy(MyClass::getStackId,
        Collectors.mapping(
          MyClass::getQuestionId, 
          Collectors.toList()
        )
      )
    );

0
你可以使用Java8的groupingBy收集器。像这样:
import org.junit.Test;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class RandomTest {

    class MyClass {
        private Long stackId;
        private Long questionId;

        public MyClass(Long stackId, Long questionId) {
            this.stackId = stackId;
            this.questionId = questionId;
        }

        public Long getStackId() {
            return stackId;
        }

        public Long getQuestionId() {
            return questionId;
        }
    }

    public class MyOtherClass {
        private Long stackId;
        private Set<Long> questionIds;

        public MyOtherClass(Long stackId, Set<Long> questionIds) {
            this.stackId = stackId;
            this.questionIds = questionIds;
        }

        public Long getStackId() {
            return stackId;
        }

        public Set<Long> getQuestionIds() {
            return questionIds;
        }
    }

    @Test
    public void test() {
        List<MyClass> classes = new ArrayList<>();
        List<MyOtherClass> otherClasses = new ArrayList<>();

        //populate the classes list
        for (int j = 1; j <= 25; j++) {
            for (int i = 0; i < 4; i++) {
                classes.add(new MyClass(0L + j, (100L*j) + i));
            }
        }

        //populate the otherClasses List
        classes.stream().collect(Collectors
                .groupingBy(MyClass::getStackId, Collectors.mapping(MyClass::getQuestionId, Collectors.toSet())))
                .entrySet().stream().forEach(
                longSetEntry -> otherClasses.add(new MyOtherClass(longSetEntry.getKey(), longSetEntry.getValue())));

        //print the otherClasses list
        otherClasses.forEach(myOtherClass -> {
            System.out.print(myOtherClass.getStackId() + ": [");
            myOtherClass.getQuestionIds().forEach(questionId-> System.out.print(questionId + ","));
            System.out.println("]");
        });
    }
}

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