如果我想汇总一个账户的当前余额列表,我可以这样做:
accountOverview.setCurrentBalance(account.stream().
filter(a -> a.getCurrentBalance() != null).
mapToLong(a -> a.getCurrentBalance()).
sum());
但是这个表达式将返回 0,即使所有余额都为空。如果所有余额都为空,我希望它返回 null,如果有非空 0 余额,则返回 0,否则返回余额之和。
如何使用 lambda 表达式执行此操作?
非常感谢
最佳答案
一旦从流中过滤掉它们,就无法知道是否所有余额都是 null
(除非检查 count()
返回的内容,否则您将无法使用该流,因为它是终端操作)。
对数据进行两次传递可能是直接的解决方案,我可能会首先采用:
boolean allNulls = account.stream().map(Account::getBalance).allMatch(Objects::isNull);
Long sum = allNulls ? null : account.stream().map(Account::getBalance).filter(Objects::nonNull).mapToLong(l -> l).sum();
您可以使用 reduce
的解决方案摆脱过滤步骤,尽管可读性可能不是最好的:
Long sum = account.stream()
.reduce(null, (l1, l2) -> l1 == null ? l2 :
l2 == null ? l1 : Long.valueOf(l1 + l2));
注意 Long.valueOf
称呼。这是为了避免条件表达式的类型是long
。 ,因此在某些边缘情况下是 NPE。
另一种解决方案是使用
Optional
应用程序接口(interface)。首先,创建一个 Stream<Optional<Long>>
从余额值中减去它们:
Optional<Long> opt = account.stream()
.map(Account::getBalance)
.flatMap(l -> Stream.of(Optional.ofNullable(l)))
.reduce(Optional.empty(),
(o1, o2) -> o1.isPresent() ? o1.map(l -> l + o2.orElse(0L)) : o2);
这会给你一个 Optional<Long>
如果所有值都是 null
,那将是空的,否则它将为您提供非空值的总和。
或者您可能想为此创建一个自定义收集器:
class SumIntoOptional {
private boolean allNull = true;
private long sum = 0L;
public SumIntoOptional() {}
public void add(Long value) {
if(value != null) {
allNull = false;
sum += value;
}
}
public void merge(SumIntoOptional other) {
if(!other.allNull) {
allNull = false;
sum += other.sum;
}
}
public OptionalLong getSum() {
return allNull ? OptionalLong.empty() : OptionalLong.of(sum);
}
}
然后:
OptionalLong opt = account.stream().map(Account::getBalance).collect(SumIntoOptional::new, SumIntoOptional::add, SumIntoOptional::merge).getSum();
如您所见,有多种方法可以实现这一点,所以我的建议是:首先选择最易读的。如果您的解决方案出现性能问题,请检查它是否可以改进(通过并行转换流或使用其他替代方案)。但是测量,不要猜测。
关于如果列表为空,Java lambda 返回 null,否则返回值的总和?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31785082/