使用Stream api减少并分配多个变量

时间:2017-06-08 13:33:45

标签: lambda java-8 java-stream

我需要在lambda中执行以下操作,但无法想到单流和减少可以帮助的方式:(

我有Employee ArrayList,其中<​​strong> name 成员变量可以在列表中的对象之间重复。我需要创建一个地图,其中key是员工姓名,value是对象,其中我们有该员工的cost1和cost2的总和

员工类有 - &gt;字符串名称,整数cost1,整数cost2

输出类有 - &gt;整数成本1,整数成本2

List <Employee> employeeList = new ArrayList<>();
// data population in inputMap
Map<String, Output> outputMap = new HashMap<>();
for (Employee emp : employeeList)
{
    Output op = outputMap.get(emp.getName());
    if(op == null){
      Output newOp = new Output ();
      newOp.setCost1(emp.getCost1())
      newOp.setCost2(emp.getCost2())
      newOp.put(emp.getName(), newOp);
    }else{
       int cost1 = op.getCost1() + emp.getCost1();
       int cost2 = op.getCost2() + emp.getCost2();
       op.setCost1(cost1);
       op.setCost2(cost2);
       newOp.put(emp.getName(), op);
    }
}

2 个答案:

答案 0 :(得分:4)

我假设您的结构类似:

static class Output {
    private final int cost1;

    private final int cost2;

    public Output(int cost1, int cost2) {
        this.cost1 = cost1;
        this.cost2 = cost2;
    } 

    @Override
    public String toString() {
        return "cost1 = " + cost1 + " cost2 = " + cost2;
    }
    // ... getter
}  

static class Employee {
    private final String name;

    private final int cost1;

    private final int cost2;

    public Employee(String name, int cost1, int cost2) {
        this.name = name;
        this.cost1 = cost1;
        this.cost2 = cost2;
    }
 // ...getters      
}

然后解决方案将是Employee::getName的第一个分组,并通过Output缩小为Collectors.reducing

  Map<String, Output> map = Arrays.asList(
                       new Employee("e", 12, 12), 
                       new Employee("f", 13, 13), 
                       new Employee("e", 11, 11))
            .stream()
            .collect(Collectors.groupingBy(Employee::getName,
                    Collectors.reducing(
                            new Output(0, 0),
                            emp -> new Output(emp.getCost1(), emp.getCost2()),
                            (left, right) -> new Output(left.getCost1() + right.getCost1(), left.getCost2() + right.getCost2()))));
    System.out.println(map); // {e=cost1 = 23 cost2 = 23, f=cost1 = 13 cost2 = 13}

答案 1 :(得分:3)

每当您需要从流的元素中获取简化地图时,您可以使用Collectors.toMap的三参数版本:

Map<String, Output> result = employeeList.stream()
    .collect(Collectors.toMap(
        Employee::getName,
        employee -> new Output(employee.getCost1(), employee.getCost2()),
        (left, right) -> {
            left.setCost1(left.getCost1() + right.getCost1());
            left.setCost2(left.getCost2() + right.getCost2());
            return left;
        }));

Collectors.toMap,顾名思义,将流的元素收集到地图中。它的第一个参数是一个将流的元素转换为键的函数,第二个参数是另一个将流的元素转换为值的函数,第三个参数是一个合并函数,当键上发生碰撞时应用于值。

在这种情况下,我在合并函数的左操作数上使用可变缩减。这是为了避免创建Output类的过多实例。减少这种方式是安全的,因为在value函数(Collectors.toMap的第二个参数)中,我正在创建Output类的新实例。

如果您可以将以下构造函数和方法添加到Output类,则可以大大改进此解决方案:

// creates an Output instance out of an Employee instance
public Output(Employee employee) {
    this.cost1 = employee.getCost1();
    this.cost2 = employee.getCost2();
}

// merges another Output instance into this Output instance
public Output merge(Output another) {
    this.cost1 += another.cost1;
    this.cost2 += another.cost2;
    return this;
}

现在,收集到地图会更简单:

Map<String, Output> result = employeeList.stream()
    .collect(Collectors.toMap(Employee::getName, Output::new, Output::merge));