Java lambda如果为空列表则返回null,否则为值的总和?

时间:2015-08-03 10:30:35

标签: java lambda

如果我想要列出一个帐户列表'目前的余额,我可以这样做:

accountOverview.setCurrentBalance(account.stream().
                filter(a -> a.getCurrentBalance() != null).
                mapToLong(a -> a.getCurrentBalance()).
                sum());

但即使所有余额都为空,此表达式也将返回0。如果所有余额都为空,我希望它返回null,如果有非空0余额则返回0,否则返回余额之和。

如何使用lambda表达式执行此操作?

非常感谢

3 个答案:

答案 0 :(得分:5)

一旦您从流中过滤了它们,就无法知道所有余额是否为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 API。首先,从余额&#39;创建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();

<小时/> 正如您所看到的,有多种方法可以实现这一点,因此我的建议是:首先选择最易读的。如果您的解决方案出现性能问题,请检查是否可以改进(通过并行转换流或使用其他替代方案)。但要衡量,不要猜。

答案 1 :(得分:3)

现在,我要这样做。想法?

        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; } }));

因为我已经过滤了空值,所以我保证不会点击任何空值。通过使初始参数减少&#39; null&#39;,我可以确保在空列表中返回null。

感觉有点困难/混乱阅读。想要一个更好的解决方案..

编辑感谢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());

答案 2 :(得分:2)

你试图做两件根本矛盾的事情:过滤掉空元素(这是一个基于单个元素的本地操作)并检测所有元素何时为空(这是一个全局操作,基于整个清单)。通常你应该将它们作为两个单独的操作来执行,这使得事情更具可读性。

除了你已经发现的reduce()伎俩之外,如果你知道余额永远不会是负数,你也可以采取不正当的伎俩,你可以做类似的事情

 long sum = account.stream().
                 mapToLong(a -> a.getCurrentBalance() == null ? 0 : a.getCurrentBalance()+1).
                 sum() - account.size();
 Long nullableSum = sum < 0 ? null : sum;

但是你必须问问自己:只有在编写了一段难以理解且相当脆弱的代码后才能通过迭代整个系列获得的收益是什么?在大多数情况下,答案将是:否。