为什么不从这个 Java 流中过滤掉空字符串?

Why aren't empty strings filtered out of this Java stream?

我有一个流操作 (Java 8),旨在将一堆字符串解析为双精度数并求和。有些字符串是空的,但我不相信任何字符串都是空的。我已经添加了一个 filter 操作来在空的到达 Double.parseDouble() 之前过滤掉它们可能会导致错误,但过滤器似乎不起作用。

我的代码:

double myTotal= myObjectList.stream()
    .map(myObject::getAmount)
    .peek( s -> System.out.println("before filter: " + s) )
    .filter( s -> !s.isEmpty() )
    .peek( s -> System.out.println("after filter: " + s) )
    .mapToDouble(Double::parseDouble)
    .reduce(0D,Double::sum);

这是在 JSP 中,JSP 编译失败。根据错误页面,异常发生在最后一行 (.reduce(0D,Double::sum);),但是 "root cause" 是一个“java.lang.NumberFormatException: empty String” -- Double.parseDouble() is encountering empty strings.

我尝试了几种 filter 操作的排列组合,包括一些检查 null 和一些检查 !s.equals("") 等。我添加了用于调试的 peek 操作,很明显 beforeafter [=12] 都打印了一个空字符串=]行。

我做错了什么?有没有更好的方法来过滤掉无法解析为双精度的值?

可能是空格,你可以先trim()字符串,然后检查它是否为空,同时删除空格字符串,如:

double myTotal= myObjectList.stream()
            .map(myObject::getAmount)
            .peek( s -> System.out.println("before filter: " + s) )
            .filter( s ->  !s.trim().isEmpty() )
            .peek( s -> System.out.println("after filter: " + s) )
            .mapToDouble(Double::parseDouble)
            .reduce(0D,Double::sum);

如果您使用 java 11,您可以在过滤器中使用 String.isBlank() 而不是 isEmpty()。 isEmpty() 查看字符串的长度。 isBlank 查找空格。

带有空格的字符串,技术上不为空,导致引发异常并显示消息 "empty string"。正如另一位发帖者指出的那样,解决方案是先 trim 输入字符串。

这种混淆是 sun.misc.FloatingDecimal.readJavaFormatString 中一个不幸的文档错误的结果,该错误被 Double.parseDouble 间接调用。虽然 String#empty 的语义是

Returns true if, and only if, length() is 0.

readJavaFormatString的实现实际上是trims输入字符串,然后计算长度,并抛出带有(非常误导的)消息 "empty string":

NumberFormatException
in = in.trim(); // don't fool around with white space.
                // throws NullPointerException if null 
int len = in.length();
if ( len == 0 ) {
    throw new NumberFormatException("empty String");
}

这可以通过执行很容易地观察到:

Double.parseDouble("   ");

您可以使用 Apache 的 StringUtils.isNotBlank 函数,因为 well.It 会同时处理 Null 和 Empty 字符串.

https://commons.apache.org/proper/commons-lang/javadocs/api-3.3/org/apache/commons/lang3/StringUtils.html#isNotBlank(java.lang.CharSequence)

double myTotal= Stream.of("","10.00","20.22","  ")
                //.map(myObject::getAmount)
                .peek( s -> System.out.println("before filter: " + s) )
                .filter(StringUtils::isNotBlank)
                .peek( s -> System.out.println("after filter: " + s) )
                .mapToDouble(Double::parseDouble)
                .reduce(0D,Double::sum);

输出:

过滤前:

过滤前:10.00

筛选后:10.00

过滤前:20.22

过滤后:20.22

过滤前:

30.22