Java 8 函數語言程式設計 如何優雅的使用Optional
Optional
Optional是Java8提供的為了解決null安全問題的一個API。
1. Optional 應該只用於返回型別
而不是引數和屬性,不然會使程式碼變的繁瑣,影響可讀性
2 你不應該簡單的呼叫 get()
Optinal的目的是為了表示此值有可能為空,在某些情況下簡單的呼叫get()而沒有先使用isPresent()進行檢查是一樣會導致空指標問題。
3 使用Optional
我們就可以把下面這樣的程式碼進行改寫。
public static String getName(User u) { if (u == null) return "Unknown"; return u.name; }
不過,千萬不要改寫成這副樣子。
public static String getName(User u) {
Optional<User> user = Optional.ofNullable(u);
if (!user.isPresent())
return "Unknown";
return user.get().name;
}
這樣改寫非但不簡潔,而且其操作還是和第一段程式碼一樣。無非就是用isPresent方法來替代u==null。這樣的改寫並不是Optional正確的用法,我們再來改寫一次。
public static String getName(User u) { return Optional.ofNullable(u) .map(user->user.name) .orElse("Unknown"); }
這樣才是正確使用Optional的姿勢。
那麼按照這種思路,我們可以安心的進行鏈式呼叫,而不是一層層判斷了。看一段程式碼:
public static String getChampionName(Competition comp) throws IllegalArgumentException { if (comp != null) { CompResult result = comp.getResult(); if (result != null) { User champion = result.getChampion(); if (champion != null) { return champion.getName(); } } } throw new IllegalArgumentException("The value of param comp isn't available."); }
由於種種原因,我們並不能開心的一路comp.getResult().getChampion().getName()到底。而其他語言比如kotlin,就提供了在語法層面的操作符加持:comp?.getResult()?.getChampion()?.getName()。所以講道理在Java裡我們怎麼辦!
讓我們看看經過Optional加持過後,這些程式碼會變成什麼樣子。
public static String getChampionName(Competition comp) throws IllegalArgumentException {
return Optional.ofNullable(comp)
.map(c->c.getResult())
.map(r->r.getChampion())
.map(u->u.getName())
.orElseThrow(()->new IllegalArgumentException("The value of param comp isn't available."));
}
這就很舒服了。Optional給了我們一個真正優雅的Java風格的方法來解決null安全問題。雖然沒有直接提供一個操作符寫起來短,但是程式碼看起來依然很爽很舒服。更何況?.這樣的語法好不好看還見仁見智呢。
還有一種實現相同作用的方式就是通過利用一個 supplier 介面來解決巢狀路徑的問題:
Outer obj = new Outer();
resolve(() -> obj.getNested().getInner().getFoo());
.ifPresent(System.out::println);
還有很多不錯的使用姿勢,比如為空則不列印可以這麼寫:
string.ifPresent(System.out::println);
Optional的魅力還不止於此,Optional還有一些神奇的用法,比如Optional可以用來檢驗引數的合法性。
orElseThrow(Supplier<? extends T> supplier)、orElseGet(Supplier<? extends T> supplier):
使用 Supplier供給型函式式介面:無引數,返回一個結果。
public void setName(String name) throws IllegalArgumentException {
this.name = Optional.ofNullable(name).filter(User::isNameValid)
.orElseThrow(()->new IllegalArgumentException("Invalid username."));
}