Java 使用streams如何创建摘要对象

Java 使用streams如何创建摘要对象,java,java-8,java-stream,reduce,Java,Java 8,Java Stream,Reduce,请假设我有以下数据结构 public class Payment { String paymentType; double price; double tax; double total; public Payment(String paymentType, double price, double tax, double total) { super(); this.paymentType = paymentType;

请假设我有以下数据结构

public class Payment {
    String paymentType;
    double price;
    double tax;
    double total;

    public Payment(String paymentType, double price, double tax, double total) {
        super();
        this.paymentType = paymentType;
        this.price = price;
        this.tax = tax;
        this.total = total;
    }
    public String getPaymentType() {
        return paymentType;
    }
    public void setPaymentType(String paymentType) {
        this.paymentType = paymentType;
    }
    public double getPrice() {
        return price;
    }
    public void setPrice(double price) {
        this.price = price;
    }
    public double getTax() {
        return tax;
    }
    public void setTax(double tax) {
        this.tax = tax;
    }
    public double getTotal() {
        return total;
    }
    public void setTotal(double total) {
        this.total = total;
    }
}
在另一种方法中,我将构建一个该类型的集合,如下所示:

private Payment generateTotal() {
    Collection<Payment> allPayments = new ArrayList<>();
    allPayments.add(new Payment("Type1", 100.01, 1.12, 101.13));
    allPayments.add(new Payment("Type2", 200.01, 2.12, 202.13));
    allPayments.add(new Payment("Type3", 300.01, 3.12, 303.13));
    allPayments.add(new Payment("Type4", 400.01, 4.12, 404.13));
    allPayments.add(new Payment("Type5", 500.01, 5.12, 505.13));

    //Generate the total with a stream and return

    return null;
}

我知道我可以使用mapToDouble一次处理一列,但我希望使用reduce或其他方法在一个流中实现这一点。

您可以将自己的
收集器
实现为
支付对象:

Payment total =
    allPayments.stream()
               .collect(Collector. of(
                   () -> new Payment("Total", 0.0, 0.0, 0.0),
                   (Payment p1, Payment p2) -> {
                       p1.setPrice(p1.getPrice() + p2.getPrice());
                       p1.setTax(p1.getTax() + p2.getTax());
                       p1.setTotal(p1.getTotal() + p2.getTotal());
                   },
                   (Payment p1, Payment p2) -> {
                       p1.setPrice(p1.getPrice() + p2.getPrice());
                       p1.setTax(p1.getTax() + p2.getTax());
                       p1.setTotal(p1.getTotal() + p2.getTotal());
                       return p1;
                   }));

您可以将自己的
收集器
实现为
支付
对象:

Payment total =
    allPayments.stream()
               .collect(Collector. of(
                   () -> new Payment("Total", 0.0, 0.0, 0.0),
                   (Payment p1, Payment p2) -> {
                       p1.setPrice(p1.getPrice() + p2.getPrice());
                       p1.setTax(p1.getTax() + p2.getTax());
                       p1.setTotal(p1.getTotal() + p2.getTotal());
                   },
                   (Payment p1, Payment p2) -> {
                       p1.setPrice(p1.getPrice() + p2.getPrice());
                       p1.setTax(p1.getTax() + p2.getTax());
                       p1.setTotal(p1.getTotal() + p2.getTotal());
                       return p1;
                   }));

没有理由使用更短、更容易阅读的流,例如:

    Payment sum = new Payment("Total", 0, 0, 0);
    allPayments.forEach(p -> {
        sum.price += p.price;
        sum.tax += p.tax;
        sum.total += p.total;
    });
正如评论中所讨论的,此解决方案不仅更短、更清晰(IMO),而且更易于维护:例如,假设您现在有一个异常:您希望继续对所有这些属性求和,但希望排除第二个索引上的项。与简单的for循环相比,将其添加到reduce-verion有多容易

同样有趣的是,这个解决方案的内存占用更小(因为reduce每次迭代都会创建一个额外的对象),并且使用提供的示例可以更高效地运行

缺点:我唯一能找到的是,如果我们正在处理的集合很大(数千个或更多),那么我们应该使用reduce解决方案与Stream.parallel一起使用,但即使这样,它也应该是

通过以下方式与JMH进行基准测试:

@Benchmark
public Payment loopIt() {
    Collection<Payment> allPayments = new ArrayList<>();
    allPayments.add(new Payment("Type1", 100.01, 1.12, 101.13));
    allPayments.add(new Payment("Type2", 200.01, 2.12, 202.13));
    allPayments.add(new Payment("Type3", 300.01, 3.12, 303.13));
    allPayments.add(new Payment("Type4", 400.01, 4.12, 404.13));
    allPayments.add(new Payment("Type5", 500.01, 5.12, 505.13));
    Payment accum = new Payment("Total", 0, 0, 0);

    allPayments.forEach(x -> {
        accum.price += x.price;
        accum.tax += x.tax;
        accum.total += x.total;
    });
    return accum;
}

@Benchmark
public Payment reduceIt() {
    Collection<Payment> allPayments = new ArrayList<>();
    allPayments.add(new Payment("Type1", 100.01, 1.12, 101.13));
    allPayments.add(new Payment("Type2", 200.01, 2.12, 202.13));
    allPayments.add(new Payment("Type3", 300.01, 3.12, 303.13));
    allPayments.add(new Payment("Type4", 400.01, 4.12, 404.13));
    allPayments.add(new Payment("Type5", 500.01, 5.12, 505.13));
    return
        allPayments.stream()
            .reduce(
                new Payment("Total", 0, 0, 0),
                (sum, each) -> new Payment(
                    sum.getPaymentType(),
                    sum.getPrice() + each.getPrice(),
                    sum.getTax() + each.getTax(),
                    sum.getTotal() + each.getTotal()));
}


没有理由使用更短、更容易阅读的流,例如:

    Payment sum = new Payment("Total", 0, 0, 0);
    allPayments.forEach(p -> {
        sum.price += p.price;
        sum.tax += p.tax;
        sum.total += p.total;
    });
正如评论中所讨论的,此解决方案不仅更短、更清晰(IMO),而且更易于维护:例如,假设您现在有一个异常:您希望继续对所有这些属性求和,但希望排除第二个索引上的项。与简单的for循环相比,将其添加到reduce-verion有多容易

同样有趣的是,这个解决方案的内存占用更小(因为reduce每次迭代都会创建一个额外的对象),并且使用提供的示例可以更高效地运行

缺点:我唯一能找到的是,如果我们正在处理的集合很大(数千个或更多),那么我们应该使用reduce解决方案与Stream.parallel一起使用,但即使这样,它也应该是

通过以下方式与JMH进行基准测试:

@Benchmark
public Payment loopIt() {
    Collection<Payment> allPayments = new ArrayList<>();
    allPayments.add(new Payment("Type1", 100.01, 1.12, 101.13));
    allPayments.add(new Payment("Type2", 200.01, 2.12, 202.13));
    allPayments.add(new Payment("Type3", 300.01, 3.12, 303.13));
    allPayments.add(new Payment("Type4", 400.01, 4.12, 404.13));
    allPayments.add(new Payment("Type5", 500.01, 5.12, 505.13));
    Payment accum = new Payment("Total", 0, 0, 0);

    allPayments.forEach(x -> {
        accum.price += x.price;
        accum.tax += x.tax;
        accum.total += x.total;
    });
    return accum;
}

@Benchmark
public Payment reduceIt() {
    Collection<Payment> allPayments = new ArrayList<>();
    allPayments.add(new Payment("Type1", 100.01, 1.12, 101.13));
    allPayments.add(new Payment("Type2", 200.01, 2.12, 202.13));
    allPayments.add(new Payment("Type3", 300.01, 3.12, 303.13));
    allPayments.add(new Payment("Type4", 400.01, 4.12, 404.13));
    allPayments.add(new Payment("Type5", 500.01, 5.12, 505.13));
    return
        allPayments.stream()
            .reduce(
                new Payment("Total", 0, 0, 0),
                (sum, each) -> new Payment(
                    sum.getPaymentType(),
                    sum.getPrice() + each.getPrice(),
                    sum.getTax() + each.getTax(),
                    sum.getTotal() + each.getTotal()));
}


您需要一个
二进制运算符累加器
来组合两个
付款
s:

public static Payment reduce(Payment p1, Payment p2) {
    return new Payment("Total", 
            p1.getPrice() + p2.getPrice(), 
            p1.getTax() + p2.getTax(), 
            p1.getTotal() + p2.getTotal()
    );
}
减少的幅度如下所示:

Payment payment = allPayments.stream().reduce(new Payment(), Payment::reduce);
或(为避免创建标识对象):

Optional oPayment=allPayments.stream().reduce(Payment::reduce);

您需要一个
二进制运算符累加器来组合两个
付款

public static Payment reduce(Payment p1, Payment p2) {
    return new Payment("Total", 
            p1.getPrice() + p2.getPrice(), 
            p1.getTax() + p2.getTax(), 
            p1.getTotal() + p2.getTotal()
    );
}
减少的幅度如下所示:

Payment payment = allPayments.stream().reduce(new Payment(), Payment::reduce);
或(为避免创建标识对象):

Optional oPayment=allPayments.stream().reduce(Payment::reduce);

我不会为此使用流,但既然您要求:

    Payment total =
            allPayments.stream()
                    .reduce(
                            new Payment("Total", 0, 0, 0),
                            (sum, each) -> new Payment(
                                    sum.getPaymentType(),
                                    sum.getPrice() + each.getPrice(),
                                    sum.getTax() + each.getTax(),
                                    sum.getTotal() + each.getTotal()));

我不会用流来做这个,但既然你问:

    Payment total =
            allPayments.stream()
                    .reduce(
                            new Payment("Total", 0, 0, 0),
                            (sum, each) -> new Payment(
                                    sum.getPaymentType(),
                                    sum.getPrice() + each.getPrice(),
                                    sum.getTax() + each.getTax(),
                                    sum.getTotal() + each.getTotal()));

使用流进行此操作的附加值是什么?使用for/foreach循环可以轻松实现您想要的功能。我们不应该为了使用流而使用流。我们使用streams/lambdas让我们的生活更轻松,而不是更困难……对不起,只是快速键入以获得想法。用编译的代码更新。流更容易阅读,也更容易理解发生了什么。错误。在某些情况下,流可能更容易读取。流不是一个银弹…写一个收集器,或者在流上做三次传递,来计算这三个和。用流做这件事的附加值是什么?使用for/foreach循环可以轻松实现您想要的功能。我们不应该为了使用流而使用流。我们使用streams/lambdas让我们的生活更轻松,而不是更困难……对不起,只是快速键入以获得想法。用编译的代码更新。流更容易阅读,也更容易理解发生了什么。错误。在某些情况下,流可能更容易读取。流不是一个银弹…写一个收集器,或者在流上做三次传递,来计算三个和。谢谢-这就是我要找的。感谢指针指向reduce。@purringdige这是一个很好的答案,但是在你的例子中,reduce的附加值是什么?如果它超过数千个对象,并且您使用
.parallel
,这将是一个很好的理由,否则,我更喜欢alfasin的答案……对于我当前的实现,此解决方案提供了我所需要的清晰性。另外,当我想到函数编程时,我想到了副作用——因此在alfasin的回答中,他在lambda中引起了副作用。我提交的答案引起的“副作用”是什么?这个解决方案在每次迭代时都会创建一个新的Payments对象,而我建议的解决方案只会创建一个用作累加器的附加对象…@purringdige这样的建议背后应该有一个理由。推理提供了何时应该遵循此建议以及何时不应该遵循此建议的上下文。不建议盲目遵循任何建议而不理解其背后的原因。正如我所写的:没有银弹,任何经验法则都应该在你想应用的时候重新考虑。谢谢——这就是我一直在寻找的。感谢指针指向reduce。@purringdige这是一个很好的答案,但是在你的例子中,reduce的附加值是什么?如果它超过数千个对象,并且您使用
.parallel
,这将是一个很好的理由,否则,我更喜欢alfasin的答案……对于我当前的实现,此解决方案提供了我所需要的清晰性。另外,当我想到函数编程时,我想到了副作用——因此在alfasin的回答中,他在lambda中引起了副作用。我提交的答案引起的“副作用”是什么?这个解决方案在每次迭代时都会创建一个新的Payments对象,而我建议的解决方案只会创建o