使用Optional,再也不頭疼NPE

前言

在 Java 語言開發中,可能大多數程序員遇到最多的異常就是 NullPointException 空指針異常了。這個當初語言的開發者「僅僅由於這樣實現起來更容易」而容許空引用所帶來的代價是很是慘痛的。而咱們開發者不得不使用多重 if 嵌套判斷來規避 NPE 或者經過多個 if 結合 return 語句來終止程序。且看一個例子java

假如須要處理下面的嵌套對象,這是一個用於汽車、汽車保險的客戶。程序員

public class Person {
    private Car car;
    public Car getCar() {
        return car;
    }
}
public class Car {
    private Insurance insurance;
    public Insurance getInsurance() {
        return insurance;
    }
}
public class Insurance {
    private String name;
    public String getName() {
        return name;
    }
}

那麼下面的代碼會存在怎樣的問題呢?數據庫

public String getCarInsuranceNames(Person person) {
    return person.getCar().getInsurance().getName();
}

沒錯,當這我的沒有車 / 他的車沒有上保險時,代碼會拋出 NPE。或者說這我的根本就是 null,也會直接拋出異常。咱們常見的做法就是在每次 get 方法以後,進行 if 判斷,增長代碼的健壯性。但是這樣代碼會顯得十分臃腫。Java 語言的開發者們也在關注着這些問題。所以在 Java8 提供了新的 API:java.util.Optional 用來優雅的處理 null。接下來就請讀者和我一塊兒揭開 Optional 神祕的面紗吧!安全

PS:Optional 類提供的不少 API 結合 Lambda 表達式食用更佳,另外還有不少 API 和 Stream 流中同名 API 的思想基本一致。所以建議讀者先行了解這兩個知識點,能夠在個人博客 Java8新特性 標籤下學習app

聲明:本文首發於博客園,做者:後青春期的Keats;地址:https://www.cnblogs.com/keatsCoder/ 轉載請註明,謝謝!框架

Optional 入門

Optional 類是一個封裝 T 值的類,變量 T 存在時,Optional 只是對他作一個簡單的封裝,若是 T 不存在,缺失的值會被建模成一個空對象,由 Optional.empty() 返回。下面這張圖能夠形象的描述 Optional 函數

1589295110199

如今咱們嘗試着重構以前關於 人 車 保險 的代碼性能

public class Person {
    private Optional<Car> car;
    public Optional<Car> getCar() {
        return car;
    }
}
public class Car {
    private Optional<Insurance> insurance;
    public Optional<Insurance> getInsurance() {
        return insurance;
    }
}
public class Insurance {
    private String name;
    public String getName() {
        return name;
    }
}

注意:對於保險來講,咱們從邏輯層面限定每一個保險公司都有名稱,若是沒有,那通常是數據出了問題而非代碼的問題,開發者應該着手去尋找爲何數據庫存在名字爲空的保險公司。而不是這裏拋出 NPE,故而咱們不用將 Insurance 的 name 字段使用 Optional 包裹學習

經過上面的代碼,咱們已經將對象由 Optional 所包裹了,那接下來咱們該如何使用它呢?ui

建立 Optional 對象

建立一個空對象

Optional<Object> empty = Optional.empty();

Optional.empty(); 該方法返回一個空對象,

根據一個非空值建立

Optional<Car> car = Optional.of(c);

Optional.of(T t); 方法會返回一個 Optional 對象,可是須要注意 ,若是 of 方法參數是 null,該行會拋出 NPE。

容許空值建立

Optional car = Optional.ofNullable(c);

爲了不在建立 Optional 對象時,因爲源對象爲空而引起的 NPE,該類還提供了 ofNullable 方法,當參數爲 null 時,返回 Optional.empty()。內部的 API 是這樣的

public static <T> Optional<T> ofNullable(T value) {
    return value == null ? empty() : of(value);
}

map --- 從 Optional 中提取和轉換值

public <U> Optional<U> map(Function<? super T, ? extends U> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent()) {
        return empty();
    } else {
        return Optional.ofNullable(mapper.apply(value));
    }
}

Optional 類提供 map 方法,接收一個函數式接口 Function 的實現類,若是調用者是空的,則返回 empty(),不然對 Optional 中的對象 value 調用 Function 實現類中的 apply() 方法,再包裝成 Optional 返回。能夠用下面的圖直觀的看到 map 執行的過程:

1589296375952

請注意,在 map 執行完 apply 方法拿到返回值以後,會主動將返回值再次包裹成 Optional 對象。所以咱們若是按照下面的方式改造咱們以前的方法,編譯是沒法經過的:

person.map(Person::getCar).map(Car::getInsurance).map(Insurance::getName);

咱們來分析一下: person.map(Person::getCar) 改造後的 person 類中, getCar 方法返回 Optional 對象,而 map 又將 Optional 包裝到 Optional 中,造成 Optional<Optional > emmm...套娃式包裝。兩層包裝的 car 是沒法經過 map 在調用 getInsurance 方法的。

幸運的是,和 Stream 同樣,Optional 也提供了扁平化流的方法 flatMap()。且看源碼

public <U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent()) {
        return empty();
    } else {
        @SuppressWarnings("unchecked")
        Optional<U> r = (Optional<U>) mapper.apply(value);
        return Objects.requireNonNull(r);
    }
}

flatMap() 比 map() 方法多了一個執行完後將嵌套 Optional 強轉成 Optional 的操做,避免了流不能繼續使用的尷尬處境。所以,咱們能夠將獲取保險公司名稱的方法改形成下面這樣:

public String getCarInsuranceName(Optional<Person> person) {
    return person.flatMap(Person::getCar)
        .flatMap(Car::getInsurance)
        .map(Insurance::getName)
        .orElse("Unknown");
}

其中 orElse() 方法表示當最終 Optional 包裹的對象仍是空時,返回的默認值

PS:因爲 Optional 並無實現序列化接口,所以若是你的項目中使用了某些要求序列化的框架,而且在某個類中使用 Optional 包裹了字段。可能會由序列化引起程序故障。

操做 Optional 中的變量

get()

經過 get() 方法獲取變量,若是變量存在就直接獲得該變量,不然拋出一個 throw new NoSuchElementException("No value present"); 異常。通常不建議使用該方法畢竟直接用 get() 方法了,還要整 Optional 這些花裏胡哨的幹啥呢

orElse()

在對象爲 null 時提供一個默認值

orElseGet(Supplier<? extends T> other)

在對象爲 null 經過調用 supplier 提供者接口的實現,返回一個值

orElseThrow()

在對象爲 null 拋出一個可定製的異常信息,能夠用來拋出項目中的自定義異常,以便全局異常捕獲器抓取及響應數據

ifPresent(Consumer<? super T> action)

當對象不爲 null 時,執行消費者操做。爲 null 時啥也不幹

更優雅的判斷語句

咱們經常調用某個對象的某個方法去判斷其屬性。爲了安全操做。首先須要對該對象進行非空校驗。例如要檢查保險公司名稱是否爲 Keats,須要這麼寫

if(i != null && "Keats".equals(i.getName())){
    System.out.println("yes");
}

如今咱們能夠這麼寫

Optional<Insurance> insurance = Optional.ofNullable(i);
insurance.filter(in -> "Keats".equals(in.getName())).ifPresent(in -> System.out.println("yes"));

先看 filter 的源碼

public Optional<T> filter(Predicate<? super T> predicate) {
    Objects.requireNonNull(predicate);
    if (!isPresent()) {
        return this;
    } else {
        return predicate.test(value) ? this : empty();
    }
}

首先第一步檢查了謂詞實現非空,第二步判斷 Optional 中的對象若是爲空則返回空 Optional,若是不爲空執行謂詞方法,條件成立則返回該對象。不然返回空 Optional。即僅當 Optional 中對象不爲 null 且符合條件時,返回該對象以後經過 ifPresent() 方法執行接下來的邏輯。很是方便易懂

其餘

Optional 還提供了一些基礎類型對象對應的類,如 OptionalInt、OptionalLong 同 Stream 流同樣,採用基本操做類型處理數據,避免了自動拆裝箱帶來的性能損失。但卻犧牲了 map、flatMap、filter 方法。開發中需酌情使用

碼字不易,若是你以爲讀完之後有收穫,不妨點個推薦讓更多的人看到吧!

相關文章
相關標籤/搜索