Tips
書中的源代碼地址:https://github.com/jbloch/effective-java-3e-source-code
注意,書中的有些代碼裏方法是基於Java 9 API中的,因此JDK 最好下載 JDK 9以上的版本。java
在Java 8以前,編寫在特定狀況下沒法返回任何值的方法時,能夠採用兩種方法。要麼拋出異常,要麼返回null(假設返回類型是對象是引用類型)。但這兩種方法都不完美。應該爲異常條件保留異常(條目 69),而且拋出異常代價很高,由於在建立異常時捕獲整個堆棧跟蹤。返回null沒有這些缺點,可是它有本身的缺陷。若是方法返回null,客戶端必須包含特殊狀況代碼來處理null返回的可能性,除非程序員可以證實null返回是不可能的。若是客戶端忽略檢查null返回並將null返回值存儲在某個數據結構中,那麼會在未來的某個時間在與這個問題不相關的代碼位置上,拋出NullPointerException
異常的可能性。git
在Java 8中,還有第三種方法來編寫可能沒法返回任何值的方法。Optional<T>
類表示一個不可變的容器,它能夠包含一個非null的T
引用,也能夠什麼都不包含。不包含任何內容的Optional被稱爲空(empty)。非空的包含值稱的Optional被稱爲存在(present)。Optional的本質上是一個不可變的集合,最多能夠容納一個元素。Optional<T>
沒有實現Collection<T>
接口,但原則上是能夠。程序員
在概念上返回T的方法,但在某些狀況下可能沒法這樣作,能夠聲明爲返回一個Optional<T>
。這容許該方法返回一個空結果,以代表不能返回有效的結果。返回Optional的方法比拋出異常的方法更靈活、更容易使用,並且比返回null的方法更不容易出錯。github
在條目 30中,咱們展現了根據集合中元素的天然順序計算集合最大值的方法。編程
// Returns maximum value in collection - throws exception if empty public static <E extends Comparable<E>> E max(Collection<E> c) { if (c.isEmpty()) throw new IllegalArgumentException("Empty collection"); E result = null; for (E e : c) if (result == null || e.compareTo(result) > 0) result = Objects.requireNonNull(e); return result; }
若是給定集合爲空,此方法將拋出IllegalArgumentException
異常。咱們在條目30中提到,更好的替代方法是返回Optional<E>
。下面是修改後的方法:數組
// Returns maximum value in collection as an Optional<E> public static <E extends Comparable<E>> Optional<E> max(Collection<E> c) { if (c.isEmpty()) return Optional.empty(); E result = null; for (E e : c) if (result == null || e.compareTo(result) > 0) result = Objects.requireNonNull(e); return Optional.of(result); }
如你所見,返回Optional很簡單。 你所要作的就是使用適當的靜態工廠建立Optional。 在這個程序中,咱們使用兩個:Optional.empty()
返回一個空的Optional,Optional.of(value)
返回一個包含給定非null值的Optional。 將null傳遞給Optional.of(value)
是一個編程錯誤。 若是這樣作,該方法經過拋出NullPointerException
異常做爲迴應。 Optional.of(value)
方法接受一個可能爲null的值,若是傳入null則返回一個空的Optional。永遠不要經過返回Optional的方法返回一個空值:它破壞Optional設計的初衷。安全
Stream
上的不少終止操做返回Optional。若是咱們重寫max方法來使用一個Stream
,那麼Stream
的max
操做會爲咱們生成Optional的工做(儘管咱們仍是傳遞一個顯式的Comparator
):數據結構
// Returns max val in collection as Optional<E> - uses stream public static <E extends Comparable<E>> Optional<E> max(Collection<E> c) { return c.stream().max(Comparator.naturalOrder()); }
那麼,如何選擇返回Optional而不是返回null或拋出異常呢?Optional
在本質上相似於檢查異常(checked exceptions)(條目 71),由於它們迫使API的用戶面對可能沒有返回任何值的事實。拋出未檢查的異常或返回null容許用戶忽略這種可能性,從而帶來潛在的可怕後果。可是,拋出一個檢查異常須要在客戶端中添加額外的樣板代碼。ide
若是方法返回一個Optional,則客戶端能夠選擇在方法沒法返回值時要採起的操做。 能夠指定默認值:性能
// Using an optional to provide a chosen default value String lastWordInLexicon = max(words).orElse("No words...");
或者能夠拋出任何適當的異常。注意,咱們傳遞的是異常工廠,而不是實際的異常。這避免了建立異常的開銷,除非它真的實際被拋出:
// Using an optional to throw a chosen exception Toy myToy = max(toys).orElseThrow(TemperTantrumException::new);
若是你能證實Optional非空,你能夠從Optional獲取值,而不須要指定一個操做來執行。可是若是Optional是空的,你判斷錯了,代碼會拋出一個NoSuchElementException
異常:
// Using optional when you know there’s a return value Element lastNobleGas = max(Elements.NOBLE_GASES).get();
有時候,可能會遇到這樣一種狀況:獲取默認值的代價很高,除非必要,不然但願避免這種代價。對於這些狀況,Optional提供了一個方法,該方法接受Supplier<T>
,並僅在必要時調用它。這個方法被稱爲orElseGet
,可是或許應該被稱爲orElseCompute
,由於它與以compute
開頭的三個Map方法密切相關。有幾個Optional的方法來處理更特殊的用例:filter
、map
、flatMap
和ifPresent
。在Java 9中,又添加了兩個這樣的方法:or
和ifPresentOrElse
。若是上面描述的基本方法與你的用例不太匹配,請查看這些更高級方法的文檔,並查看它們是否可以完成任務。
若是這些方法都不能知足你的須要,Optional提供isPresent()
方法,能夠將其視爲安全閥。若是Optional包含值,則返回true;若是爲空,則返回false。你可使用此方法對可選結果執行任何喜歡的處理,但請確保明智地使用它。isPresent
的許多用途均可以被上面提到的一種方法所替代。生成的代碼一般更短、更清晰、更符合習慣。
例如,請考慮此代碼段,它打印一個進程的父進程ID,若是進程沒有父進程,則打印N/A. 該代碼段使用Java 9中引入的ProcessHandle
類:
Optional<ProcessHandle> parentProcess = ph.parent(); System.out.println("Parent PID: " + (parentProcess.isPresent() ? String.valueOf(parentProcess.get().pid()) : "N/A"));
上面的代碼能夠被以下代碼所替代,使用了Optional的map
方法:
System.out.println("Parent PID: " + ph.parent().map(h -> String.valueOf(h.pid())).orElse("N/A"));
當使用Stream進行編程時,一般會發現使用的是一個Stream<Optional<T>>
,而且須要一個Stream<T>
,其中包含非Optional中的全部元素,以便繼續進行。若是你正在使用Java 8,下面是彌補這個差距的代碼:
streamOfOptionals .filter(Optional::isPresent) .map(Optional::get)
在Java 9中,Optional配備了一個stream()
方法。這個方法是一個適配器, 此方法是一個適配器,它將Optional變爲包含一個元素的Stream,若是Optional爲空,則不包含任何元素。此方法與Stream的flatMap
方法(條目45)相結合,這個方法能夠簡潔地替代上面的方法:
streamOfOptionals. .flatMap(Optional::stream)
並非全部的返回類型都能從Optional的處理中獲益。容器類型,包括集合、映射、Stream、數組和Optional,不該該封裝在Optional中。與其返回一個空的Optional<List<T>>
,不還如返回一個空的List<T>
(條目 54)。返回空容器將消除客戶端代碼處理Optional的須要。ProcessHandle
類確實有arguments
方法,它返回Optional<String[]>
,可是這個方法應該被視爲一種異常,不應被效仿。
那麼何時應該聲明一個方法來返回Optional <T>
而不是T
呢? 一般,若是可能沒法返回結果,而且在沒有返回結果,客戶端還必須執行特殊處理的狀況下,則應聲明返回Optional
Optional <T>
並不是沒有成本。 Optional是必須分配和初始化的對象,從Optional中讀取值須要額外的迂迴。 這使得Optional不適合在某些性能關鍵的狀況下使用。 特定方法是否屬於此類別只能經過仔細測量來肯定(條目 67)。
與返回裝箱的基本類型相比,返回包含已裝箱基本類型的Optional的代價高得驚人,由於Optional有兩個裝箱級別,而不是零。所以,類庫設計人員認爲爲基本類型int、long和double提供相似Option
OptionalInt
、
OptionalLong
和
OptionalDouble
。它們包含
Optional<T>
上的大多數方法,但不是全部方法。所以,除了「次要基本類型(minor primitive types)」Boolean,Byte,Character,Short和Float以外,
永遠不該該返回裝箱的基本類型的Optional。
到目前爲止,咱們已經討論了返回Optional並在返回後處理它們的方法。咱們尚未討論其餘可能的用法,這是由於大多數其餘Optional的用法都是可疑的。例如,永遠不要將Optional用做映射值。若是這樣作,則有兩種方法能夠表示鍵(key)在映射中邏輯上的缺失:鍵要麼不在映射中,要麼存在的話映射到一個空的Optional。這反映了沒必要要的複雜性,頗有可能致使混淆和錯誤。更通俗地說,在集合或數組中使用Optional的鍵、值或元素幾乎都是不合適的。
這裏留下了一個懸而未決的大問題。在實例中存儲Optional屬性是否合適嗎?一般這是一種「很差的味道」:它建議你可能應該有一個包含Optional屬性的子類。但有時這多是合理的。考慮條目2中的NutritionFacts
類的狀況。NutritionFacts
實例包含許多不須要的屬性。不可能爲這些屬性的每一個可能組合都提供一個子類。此外,屬性包含基本類型,這使得很難直接表示這種缺失。對於NutritionFacts
最好的API將爲每一個Optional屬性從getter方法返回一個Optional,所以將這些Optional做爲屬性存儲在對象中是頗有意義的。
總之,若是發現本身編寫的方法不能老是返回值,而且認爲該方法的用戶在每次調用時考慮這種可能性很重要,那麼或許應該返回一個Optional的方法。可是,應該意識到,返回Optional會帶來實際的性能後果;對於性能關鍵的方法,最好返回null或拋出異常。最後,除了做爲返回值以外,不該該在任何其餘地方中使用Optional。