1. 程式人生 > >Java 8 函數語言程式設計 如何優雅的使用Optional

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."));
}