如果为空列表,则 Java lambda 返回 null,否则返回值的总和?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/31785082/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Java lambda to return null if empty list otherwise sum of values?
提问by user384842
If I want to total a list of accounts' current balances, I can do:
如果我想汇总帐户的当前余额列表,我可以这样做:
accountOverview.setCurrentBalance(account.stream().
filter(a -> a.getCurrentBalance() != null).
mapToLong(a -> a.getCurrentBalance()).
sum());
But this expression will return 0, even if all the balances are null. I would like it to return null if all the balances are null, 0 if there are non-null 0 balances, and the sum of the balances otherwise.
但是这个表达式将返回 0,即使所有余额都为空。如果所有余额为空,我希望它返回 null,如果有非空 0 余额,则返回 0,否则返回余额总和。
How can I do this with a lambda expression?
如何使用 lambda 表达式执行此操作?
Many thanks
非常感谢
回答by Alexis C.
Once you filtered them from the stream, there's no way to know if all the balances were null
(unless check what count()
returns but then you won't be able to use the stream since it's a terminal operation).
一旦您从流中过滤掉它们,就无法知道是否所有余额都是null
(除非检查count()
返回的是什么,但您将无法使用流,因为它是一个终端操作)。
Doing two passes over the data is probably the straight-forward solution, and I would probably go with that first:
对数据进行两次传递可能是直接的解决方案,我可能会先使用它:
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();
You could get rid of the filtering step with your solution with reduce
, although the readability maybe not be the best:
reduce
尽管可读性可能不是最好的,但您可以使用 解决方案摆脱过滤步骤:
Long sum = account.stream()
.reduce(null, (l1, l2) -> l1 == null ? l2 :
l2 == null ? l1 : Long.valueOf(l1 + l2));
Notice the Long.valueOf
call. It's to avoid that the type of the conditional expression is long
, and hence a NPE on some edge cases.
注意Long.valueOf
通话。这是为了避免条件表达式的类型是long
,因此在某些边缘情况下是 NPE。
另一种解决方案是使用
Optional
Optional
API。首先,Stream<Optional<Long>>
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);
This will give you an Optional<Long>
that will be empty if all the values were null
, otherwise it'll give you the sum of the non-null values.
Optional<Long>
如果所有值都是null
,这将为您提供一个空值,否则它将为您提供非空值的总和。
Or you might want to create a custom collector for this:
或者您可能想为此创建一个自定义收集器:
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);
}
}
and then:
接着:
OptionalLong opt = account.stream().map(Account::getBalance).collect(SumIntoOptional::new, SumIntoOptional::add, SumIntoOptional::merge).getSum();
如您所见,有多种方法可以实现这一点,所以我的建议是:首先选择最易读的。如果您的解决方案出现性能问题,请检查它是否可以改进(通过并行转换流或使用其他替代方法)。但是衡量,不要猜测。
回答by user384842
For now, I'm going with this. Thoughts?
现在,我要这样做。想法?
accountOverview.setCurrentBalance(account.stream().
filter(a -> a.getCurrentBalance() != null).
map(a -> a.getCurrentBalance()).
reduce(null, (i,j) -> { if (i == null) { return j; } else { return i+j; } }));
Because I've filtered nulls already, I'm guaranteed not to hit any. By making the initial param to reduce 'null', I can ensure that I get null back on an empty list.
因为我已经过滤了空值,所以我保证不会命中任何空值。通过使初始参数减少“空”,我可以确保在空列表中返回空值。
Feels a bit hard/confusing to read though. Would like a nicer solution..
虽然读起来有点困难/令人困惑。想要更好的解决方案..
EDIT Thanks to pbabcdefp, I've gone with this rather more respectable solution:
编辑感谢 pbabcdefp,我已经采用了这个更受人尊敬的解决方案:
List<Account> filtered = account.stream().
filter(a -> a.getCurrentBalance() != null).
collect(Collectors.toList());
accountOverview.setCurrentBalance(filtered.size() == 0?null:
filtered.stream().mapToLong(a -> a.getCurrentBalance()).
sum());
回答by biziclop
You're trying to do two fundamentally contradicting things: filter out null elements (which is a local operation, based on a single element) and detect when all elements are null (which is a global operation, based on the entire list). Normally you should do these as two separate operations, that makes things a lot more readable.
您正在尝试做两个根本矛盾的事情:过滤掉空元素(这是一个本地操作,基于单个元素)并检测何时所有元素都为空(这是一个全局操作,基于整个列表)。通常,您应该将这些作为两个单独的操作来执行,这会使事情更具可读性。
Apart from the reduce()
trick you've already found, you can also resort to underhand tricks, if you know that balance can never be negative for example, you can do something like
除了reduce()
你已经找到的技巧之外,你还可以使用一些不为人知的技巧,例如,如果你知道平衡永远不会是负数,你可以做一些类似的事情
long sum = account.stream().
mapToLong(a -> a.getCurrentBalance() == null ? 0 : a.getCurrentBalance()+1).
sum() - account.size();
Long nullableSum = sum < 0 ? null : sum;
But you've got to ask yourself: is what you gain by only iterating across your collection once worth the cost of having written a piece of unreadable and fairly brittle code? In most cases the answer will be: no.
但是你必须问自己:你只在你的集合中迭代一次所获得的收益是否值得编写一段不可读且相当脆弱的代码?在大多数情况下,答案是:不。