2017-07-07 60 views
4

鑑於這種代碼:的java 8的FindFirst VS地圖上可選

class Foo { 
    Integer attr; 
    public Integer getAttr() {return attr;} 
} 

List<Foo> list = new ArrayList<>(); 
list.add(new Foo()); 

list.stream().map(Foo::getAttr).findAny().orElse(null); //A 
list.stream().findAny().map(Foo::getAttr).orElse(null); //B 

A線拋出

顯示java.lang.NullPointerException:空

而線B返回null。

這種行爲的原因是什麼? findAny()map()返回Optional<T>

+1

參見[爲什麼使用FindFirst()拋出NullPointerException如果它發現的第一個元素是空?](https://stackoverflow.com/q/32466799/2711488) – Holger

回答

5
list.stream().map(Foo::getAttr).findAny().orElse(null); 

Java doc for streams說,流:「返回一個由施加給定的功能,以該流中的元素的結果的流」,和findAny()「可以返回aNullPointerException - 如果所選擇的元素是空」。在你的類Foo中,Integer(不是int)在默認情況下被設置爲null,因爲它被聲明瞭但沒有被初始化。見Primitives see default valuesObject initialization in Java

初始化是不同爲: A)類成員(Object和基本類型) B)局部變量

+0

您應該編輯你的答案添加源);我知道他們,但這是更好的 – AxelH

0
list.stream().map(Foo::getAttr) 

...返回一個流與一個元件,具有空的值。

findAny()(和findFirst())的JavaDoc的說:

返回:

可選的描述本流的一些元件或 空可選如果流是空

拋出:

NullPointerException - 如果選擇的元素爲null

所以findAny()正好做的記錄:它選擇一個空的,因此,拋NullPointerException

這是有意義的,因爲Optional是(再次根據JavaDoc的,但重點煤礦):

的容器對象,其可以或可以不包含一個非空

。 ..這意味着你可以保證Optional.ifPresent(x -> x.method())將永遠不會拋出NullPointerException,因爲x爲空。因此findAny()無法返回Optional.of(null)。而Optional.empty()表示該流爲空,而不是它找到空。

Stream/Optional基礎架構的很多部分都是關於阻止使用空值。

您可以通過將空值映射到Optionals來產生Optional<Optional<Foo>> - 這看起來有點複雜,但它是對您的域的精確表示。 Optional.empty()表示該流爲空。 Optional.of(Optional.empty())意味着它發現一個空元素:

list.stream().map(Foo::getAttr).map(Optional::ofNullable).findAny() 
+3

使用'flatMap'會使它更清潔:'list.stream()。findAny()。flatMap(f - > Optional.ofNullable(f.getAttr()))。 orElse(null);'就像我上面寫的那樣... – Eugene

+1

@Eugene如果你想讓它失去它,是的。在我看來,這個代碼可以是一個可以存在空值的域和一個保證非空的域的邊界。 – slim

3

那麼這顯然是因爲要執行這些操作,也因爲findAny明確地說,訂單:throws NullPointerException if the element selected is null

當你做map(Foo::getAttr)你已經有效地映射,要null,所以你的Stream現在包含一個null;從而findAny場所因出現異常(因爲findAny施加在該空)

,其它的操作首先找到Foo對象,然後將其映射到Foo::getAttr(從而將其映射到Optional.empty()),從而orElse被調用。

而且,這將(至少對我來說)更有意義:

list.stream() 
    .findAny() 
    .flatMap(f -> Optional.ofNullable(f.getAttr())) 
    .orElse(null); 

flatMap將映射到Optional<Integer>(屬性),如果這個人是empty得到orElse結果。

+3

Ahem,不可能有'可選'映射到'null'。一個'Optional'或者是空的,或者映射到一個非'null'值。 OP的第二種情況,在可選項上調用map(Foo :: getAttr),與你的flatMap(f - > Optional.ofNullable(f.getAttr()))完全一樣。無論哪種情況,結果都是空的可選參數,並且返回傳遞給'orElse'的參數。 (並且說「orElse'沒有被調用」無論如何都沒有意義,沒有辦法調用'orElse') – Holger

+0

@Holger gosh從我這邊是愚蠢的,謝謝 – Eugene

2

首先,你的兩個代碼片段map是不同的操作:

//   v--- stream intermediate operation 
list.stream().map(Foo::getAttr).findAny().orElse(null); //A 
//      v---- a Optional utility method 
list.stream().findAny().map(Foo::getAttr).orElse(null); //B 

和發生在Stream#findAny操作NullPointerException,因爲它不能接受null值。由於它使用Optional.of而不是Optional.ofNullable。和Stream#findAny文檔已經斷言:

拋出

NullPointerException - 如果選擇的元素是

所以如果你想你的A代碼段工作正常,你必須在調用Stream#findAny之前過濾所有null值,例如:

//when no elements in stream, `findAny` will be return a empty by Optional.empty() 
//              v 
list.stream().map(Foo::getAttr).filter(Objects::nonNull).findAny().orElse(null);//A 
+1

幹得好!我認爲沒有人會談論「過濾器(Objects :: nonNull)」 –