根据流

时间:2016-07-05 20:33:30

标签: java stream java-8 grouping

我有一个对象列表,如下所示:

{
    value=500
    category="GROCERY"
},
{
    value=300
    category="GROCERY"
},
{
    value=100
    category="FUEL"
},
{
    value=300
    category="SMALL APPLIANCE REPAIR"
},
{
    value=200
    category="FUEL"
}

我想将其转换为看起来像这样的对象列表:

{
    value=800
    category="GROCERY"
},
{
    value=300
    category="FUEL"
},
{
    value=300
    category="SMALL APPLIANCE REPAIR"
}

基本上将所有值加在同一类别中。

我应该使用flatMap吗?降低?我不明白这些的细微差别来弄明白。

帮助?

编辑:

这个问题有很多重复: Is there an aggregateBy method in the stream Java 8 api?Sum attribute of object with Stream API

但在这两种情况下,最终结果都是地图,而不是列表

根据@AndrewTobilko和@JBNizet的回答,我使用的最终解决方案是:

List<MyClass> myClassList = list.stream()
    .collect(Collectors.groupingBy(YourClass::getCategory,
                    Collectors.summingInt(YourClass::getValue)))
    .entrySet().stream().map(e -> new MyClass(e.getKey(), e.getValue()).collect(toList());

3 个答案:

答案 0 :(得分:5)

Collectors类提供了一个“groupingBy”,允许您对流执行“分组依据”操作(类似于数据库中的GROUP BY)。假设您的对象列表是“对象”类型,以下代码应该起作用:

Map<String, Integer> valueByCategory = myObjects.stream().collect(Collectors.groupingBy(MyObjects::getCategory, Collectors.summingInt(MyObjects::getValue)));

代码基本上按照每个类别对您的流进行分组,并在每个组上运行一个收集器,该收集器总结每个流元素的getValue()的返回值。 见https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html

答案 1 :(得分:2)

使用Collectors类的静态导入:

list.stream().collect(groupingBy(Class::getCategory, summingInt(Class::getValue)));

您将获得地图Map<String, Integer>Class必须使用getValuegetCategory方法来编写方法引用,例如

public class Class {
    private String category;
    private int value;

    public String getCategory() { return category; }
    public int getValue() { return value; }
}

答案 2 :(得分:0)

基于减少的方法:

List<Obj> values = list.stream().collect(
        Collectors.groupingBy(Obj::getCategory, Collectors.reducing((a, b) -> new Obj(a.getValue() + b.getValue(), a.getCategory())))
).values().stream().map(Optional::get).collect(Collectors.toList());

次要stream()调用重新映射来自Optional<Obj>和中间Map<String, Optional<Obj>>对象的结果,这是不好的。

我可以使用排序建议替代变体(不太可读):

List<Obj> values2 = list.stream()
    .sorted((o1, o2) -> o1.getCategory().compareTo(o2.getCategory()))
    .collect(
        LinkedList<Obj>::new,
        (ll, obj) -> {
            Obj last = null;
            if(!ll.isEmpty()) {
                last = ll.getLast();
            }

            if (last == null || !last.getCategory().equals(obj.getCategory())) {
                ll.add(new Obj(obj.getValue(), obj.getCategory())); //deep copy here
            } else {
                last.setValue(last.getValue() + obj.getValue());
            }
        },
        (list1, list2) -> {
              //for parallel execution do a simple merge join here
              throw new RuntimeException("parallel evaluation not supported"); 
         }
    );

这里我们按类别对Obj的列表进行排序,然后按顺序处理它,压缩来自同一类别的连续对象。

不幸的是,Java中没有方法可以在不手动保留最后一个元素或元素列表的情况下执行此操作(另请参阅Collect successive pairs from a stream

可以在此处检查两个代码段的工作示例:https://ideone.com/p3bKV8