Java 8:通过使用流对不同字段进行分组和求和,将List <objecta>转换为List <objectb>

时间:2017-05-06 14:15:13

标签: java java-8 java-stream

我正在尝试理解java 8流。我有两节课。

public class ObjectA {
    private String fieldA;
    private String fieldB;
    private String fieldC;
    private double valueA;
    private double valueB;
    private double valueC;      
}

public class ObjectB {
    private String fieldA;
    private double valueA;
    private double valueB;
    private double valueC; 
}

我正在尝试将List<ObjectA>转换为List<ObjectB>,方法是对fieldA进行分组并使用流汇总valueAvalueBvalueC,但无法弄清楚如何做到这一点。

这是我想要做的:

Map<String,ObjectB> fieldCObjectBMap = new HashMap<>();

for(ObjectA objectA : objectAList) {
  if(fieldCObjectBMap.size != 0 && fieldCObjectBMap.keyset().contains(objectA.getFieldC)) {
    ObjectB objectB = fieldCObjectBMap.get(objectA.getFieldC);
    objectB.setValueA(objectB.getValueA()+objectA.getValueA());
    objectB.setValueB(objectB.getValueB()+objectA.getValueB());
    objectB.setValueC(objectB.getValueC()+objectA.getValueC());
    fieldCObjectBMap.put(objectA.getFieldC,objectB);
  } else {
    ObjectB objectB = fieldCObjectBMap.get(objectA.getFieldC);
    objectB.setValueA(objectA.getValueA());
    objectB.setValueB(objectA.getValueB());
    objectB.setValueC(objectA.getValueC());
    fieldCObjectBMap.put(objectA.getFieldC,objectB);
  }
}

List<ObjectB> objectBList = fieldCObjectBMap.values();

2 个答案:

答案 0 :(得分:2)

根据您描述的规则建议将List<ObjectA>转换为List<ObjectB>的转换器:

class AtoBCollector {
    private static ObjectB makeFromA(ObjectA a) {
        return new ObjectB(a.getFieldA(), a.getValueA(), a.getValueB(), a.getValueC());
    }

    private static BinaryOperator<ObjectB> reduceB = (b1, b2) ->
        new ObjectB(b1.getFieldA(),
                    b1.getValueA() + b2.getValueA(),
                    b1.getValueB() + b2.getValueB(),
                    b1.getValueC() + b2.getValueC());

    static List<ObjectB> collect(List<ObjectA> objectAList) {
        return new ArrayList<>(objectAList.stream()
            .map(AtoBCollector::makeFromA)
            .collect(toMap(ObjectB::getFieldA, identity(), reduceB))
            .values());
    }
}

toMap的作用是:第一次遇到fieldA的值时,它会将Map<String, ObjectB>作为键,将其作为ObjectB的相应实例的值。下次它将合并两个ObjectB个实例与reduceB

请注意,values()会返回Collection,您必须明确创建List。在许多情况下,您可以直接使用Collection,这是首选。

作为上述内容的替代方案,您可以内联makeFromAreduceB并跳过AtoBCollector的定义。某些人认为结果代码的可读性较差。做出最好的判断。

作为另一种选择,如果ObjectAObjectB的源代码在您的控制之下,您可以将makeFromA的功能实现为ObjectB的构造函数和/或reduceB作为ObjectB的方法。

答案 1 :(得分:1)

一种方法是使用Collectors.toMap作为@Manos Nikolaidis在答案中所做的。另一种方法是使用自定义收集器。我将在静态帮助器方法中执行此操作,使用本地类Acc来累积部分结果:

static Collector<ObjectA, ?, ObjectB> summingFields() {

    class Acc {
        ObjectB b = new ObjectB();

        void sum(ObjectA a) {
            this.b.setFieldA(a.getFieldA());
            this.b.setValueA(b.getValueA() + a.getValueA());
            this.b.setValueB(b.getValueB() + a.getValueB());
            this.b.setValueC(b.getValueC() + a.getValueC());
        }

        Acc merge(Acc other) {
            this.b.setValueA(this.b.getValueA() + other.b.getValueA());
            this.b.setValueB(this.b.getValueB() + other.b.getValueB());
            this.b.setValueC(this.b.getValueC() + other.b.getValueC());
            return this;
        }

        ObjectB getB() {
            return this.b;
        }
    }
    return Collector.of(Acc::new, Acc::sum, Acc::merge, Acc::getB);
}

这使用方法Collector.of,它根据其参数创建自定义收集器。然后,您可以使用summingFields方法获取Collection<ObjectB>,如下所示:

Collection<ObjectB> grouped = objectAList.stream()
    .collect(Collectors.groupingBy(
        ObjectA::getFieldA,
        summingFields()))
    .values();

但是,如果您可以在ObjectB类中添加以下几种方法,那么一切都会轻松得多:

public void sum(ObjectA a) {
    this.fieldA = a.getFieldA();
    this.valueA += a.getValueA();
    this.valueB += a.getValueB();
    this.valueC += a.getValueC();
}

public ObjectB merge(ObjectB b) {
    this.valueA += b.getValueA();
    this.valueB += b.getValueB();
    this.valueC += b.getValueC();
    return this;
}

然后,您可以使用Collector.of这些方法:

Collection<ObjectB> grouped = objectAList.stream()
    .collect(Collectors.groupingBy(
        ObjectA::getFieldA,
        Collector.of(ObjectB::new, ObjectB::sum, ObjectB::merge)))
    .values();