我们应该如何为空值管理 jdk8流

我知道这个主题可能有点 in advance,因为 JDK8还没有发布(至少现在还没有发布)。.)但是我读了一些关于 Lambda 表达式的文章,特别是关于新的集合 API Stream 的部分。

下面是 Java 杂志的文章中给出的例子(这是一个水獭种群算法. .) :

Set<Otter> otters = getOtters();
System.out.println(otters.stream()
.filter(o -> !o.isWild())
.map(o -> o.getKeeper())
.filter(k -> k.isFemale())
.into(new ArrayList<>())
.size());

我的问题是,如果在 Set 内部迭代的中间,有一个水獭为 null,会发生什么?

我希望抛出一个 NullPointerException,但也许我仍然停留在以前的开发范例(非功能性)中,有人能告诉我应该如何处理这个异常吗?

如果这真的抛出了 NullPointerException,我发现这个特性非常危险,只能按以下方式使用:

  • 开发人员,以确保没有空值(可以使用以前的 . filter (o-> o! = null)
  • 开发人员,以确保应用程序永远不会 生成 NullOtter 或者一个特殊的 NullOtter 对象来处理。

什么是最好的选择,或者任何其他的选择?

169154 次浏览

Current thinking seems to be to "tolerate" nulls, that is, to allow them in general, although some operations are less tolerant and may end up throwing NPE. See the discussion of nulls on the Lambda Libraries expert group mailing list, specifically this message. Consensus around option #3 subsequently emerged (with a notable objection from Doug Lea). So yes, the OP's concern about pipelines blowing up with NPE is valid.

It's not for nothing that Tony Hoare referred to nulls as the "Billion Dollar Mistake." Dealing with nulls is a real pain. Even with classic collections (without considering lambdas or streams) nulls are problematic. As fge mentioned in a comment, some collections allow nulls and others do not. With collections that allow nulls, this introduces ambiguities into the API. For example, with Map.get(), a null return indicates either that the key is present and its value is null, or that the key is absent. One has to do extra work to disambiguate these cases.

The usual use for null is to denote the absence of a value. The approach for dealing with this proposed for Java SE 8 is to introduce a new java.util.Optional type, which encapsulates the presence/absence of a value, along with behaviors of supplying a default value, or throwing an exception, or calling a function, etc. if the value is absent. Optional is used only by new APIs, though, everything else in the system still has to put up with the possibility of nulls.

My advice is to avoid actual null references to the greatest extent possible. It's hard to see from the example given how there could be a "null" Otter. But if one were necessary, the OP's suggestions of filtering out null values, or mapping them to a sentinel object (the Null Object Pattern) are fine approaches.

Stuart's answer provides a great explanation, but I'd like to provide another example.

I ran into this issue when attempting to perform a reduce on a Stream containing null values (actually it was LongStream.average(), which is a type of reduction). Since average() returns OptionalDouble, I assumed the Stream could contain nulls but instead a NullPointerException was thrown. This is due to Stuart's explanation of null v. empty.

So, as the OP suggests, I added a filter like so:

list.stream()
.filter(o -> o != null)
.reduce(..);

Or as tangens pointed out below, use the predicate provided by the Java API:

list.stream()
.filter(Objects::nonNull)
.reduce(..);

From the mailing list discussion Stuart linked: Brian Goetz on nulls in Streams

An example how to avoid null e.g. use filter before groupingBy

Filter out the null instances before groupingBy.

Here is an example

MyObjectlist.stream()
.filter(p -> p.getSomeInstance() != null)
.collect(Collectors.groupingBy(MyObject::getSomeInstance));

If you just want to filter null values out of a stream, you can simply use a method reference to java.util.Objects.nonNull(Object). From its documentation:

This method exists to be used as a Predicate, filter(Objects::nonNull)

For example:

List<String> list = Arrays.asList( null, "Foo", null, "Bar", null, null);


list.stream()
.filter( Objects::nonNull )  // <-- Filter out null values
.forEach( System.out::println );

This will print:

Foo
Bar

Although the answers are 100% correct, a small suggestion to improve null case handling of the list itself with Optional:

 List<String> listOfStuffFiltered = Optional.ofNullable(listOfStuff)
.orElseGet(Collections::emptyList)
.stream()
.filter(Objects::nonNull)
.collect(Collectors.toList());

The part Optional.ofNullable(listOfStuff).orElseGet(Collections::emptyList) will allow you to handle nicely the case when listOfStuff is null and return an emptyList instead of failing with NullPointerException.

If you do not want to iterate two times (filter + map or any). Try this.

private static void trimAll() {
String[] emtLne = {"", " ", "  cc  ", null, "      xx   "};
System.out.println(Arrays.stream(emtLne).map(val -> (val != null) ? val.trim() : "").collect(Collectors.joining()));
}

Output
ccxx