如何正確的使用Java 8的新特性之 Optional

使用 Java8 有一段時間了,對于其中的 Optional 類使用較為頻繁,所以寫一篇文章記錄

我不會說是因為老記不住調(diào)用 Api才寫的

Optional 類主要解決的問題是 Java 常見的的空指針異常 NullPointerException

從創(chuàng)建 OptionalAPI 來看,可以創(chuàng)建內(nèi)容不為空或內(nèi)容為空的類

同時,Optional 也是用來實現(xiàn) 函數(shù)式編程 的一個很大的進(jìn)步,雖然代碼精煉了,但是從 代碼可讀性 上來說并不友好。所以,根據(jù)實際業(yè)務(wù)場景來合理使用

場景模擬

一個學(xué)生類,其中包含姓名、班級類
班級類中包含課程類、教室位置等信息
課程類包括授課老師、課程名稱等信息

假設(shè):一個學(xué)生對應(yīng)一個班級,一個班級對應(yīng)一門課程
根據(jù)學(xué)生類→獲取班級類→獲取課程類

如果你想獲取學(xué)生報的課程,那么代碼是這樣的

String courseName = student.getSchoolClass().getCourse().getCourseName();

這樣的話是很可能發(fā)生空指針異常,如果想控制這個異常,代碼是這樣的

if (student != null) {
    if (student.getSchoolClass() != null) {
        SchoolClass schoolClass = student.getSchoolClass();
        if (schoolClass != null) {
            Course course = schoolClass.getCourse();
            if (course != null) {
                String courseName = course.getCourseName();
            }
        }
    }
}

這種代碼邏輯上確實感覺很明了,但是總覺得有一絲絲不優(yōu)雅。可以看下 Optional 類的一些使用,看能否進(jìn)行簡化并控制異常的拋出

創(chuàng)建實例

Optional 類有三個創(chuàng)建實例的方法,分別是 Optional.empty()、Optional.of(value)、Optional.ofNullable(value),看分別對應(yīng)什么樣的需求和場景

Optional.empty()

此方法是聲明一個空的Optional

Optional<String> str = Optional.empty();

這種方式創(chuàng)建出來的類在被賦值前不能夠被訪問,如果進(jìn)行訪問的話將會拋出異常

java.util.NoSuchElementException: No value present

可以使用另外一種方式來防止空值傳入

Optional.of(value)

依據(jù)非空值創(chuàng)建一個Optional,如果傳入值為空,會直接拋出 NullPointerException

Optional<String> nameStr = Optional.of("百萬");
Optional.ofNullable(value)

如果對象即可能是 空 也可能是非 空,那么應(yīng)該使用 Optional.ofNullable(value) 方法

訪問 Optional 對象的值

使用 Optional 創(chuàng)建出來的對象是不能夠直接使用,而是需要使用 .get() 方法獲取到其中的值

@Test
public void testOptionalGet() {
    String name = "馬馬馬馬馬百萬";
    Optional<String> opt = Optional.ofNullable(name);
    System.out.println(opt.get());
}

檢查 Optional 對象不為空

檢查 Optional 對象不為空有兩種方式,分別是 isPresent()、ifPresent()

isPresent()

如果Optional 調(diào)用了 isPresent() 方法,那么會返回一個 boolean

@Test
public void testOptionalIsPresent() {
    String name = "馬馬馬馬馬百萬";
    Optional<String> opt = Optional.ofNullable(name);
    if (opt.isPresent()) {
        System.out.println(opt.get());
    }
}
ifPresent()

此方法不僅可以檢查 Optional 是否為空,還有一個 Consumer(消費者) 參數(shù),如果不為空,執(zhí)行傳入的 lambda 表達(dá)式

@Test
public void testOptionalGet() {
    String name = "馬馬馬馬馬百萬";
    Optional<String> opt = Optional.ofNullable(name);
    opt.ifPresent(data -> System.out.println(opt.get()));
}

返回默認(rèn)值

如果 Optional 為空返回默認(rèn)值提供了兩個方法,分別是 orElse()、orElseGet()

orElse()

因為 student 為空,所以 target 值為 student2
如果 student 不為空,那么返回值就會是 student 本身

@Test
public void testOptionalOrElse() {
    Student student = null;
    Student student2 = new Student("馬馬馬馬馬百萬", 26);
    Student target = Optional.ofNullable(student).orElse(student2);
}
orElseGet()

orElseGet() 方法在有值時返回本身,值為空時,它會執(zhí)行作為參數(shù)傳入的 Supplier(供應(yīng)者) 函數(shù)式接口,并將返回其執(zhí)行結(jié)果

@Test
public void testOptionalOrElseGet() {
    Student student = null;
    Student target = Optional.ofNullable(student).orElseGet(() -> new Student("馬馬馬馬馬百萬", 26));
}
orElse() 和 orElseGet() 的不同之處

orElse()orElseGet() 在值為空時處理結(jié)果是一致的;But 在值不為空時,又是一番風(fēng)景

@Test
public void testOptionalOrElse() {
    Student student = null;
    log.info("student orElse");
    Student result1 = Optional.ofNullable(student).orElse(createStudent());
    log.info("student orElseGet");
    Student result2 = Optional.ofNullable(student).orElseGet(() -> createStudent());
}

public Student createStudent() {
    log.info("creating student");
    return new Student("馬馬馬馬馬百萬", 26);
}

打印輸出

student orElse
creating student
student orElseGet

通過打印日志看出,orElseGet()student 不為空時沒有調(diào)用 createStudent() 方法,反觀 orElse() 仍然執(zhí)行了創(chuàng)建方法

所以不推薦使用 orElse() 作為返回默認(rèn)值方法,當(dāng)然在 一般情況 下,這種微量消耗不會出現(xiàn)問題

異常拋出 orElseThrow()

Optional 定義 orElseThrow() 作為異常拋出的 API,它會在對象為空時拋出一個異常
拋出的異常不一定非要是 RuntimeException(),也可以是其它異?;蝽椖孔远x異常等

@Test
public void testOptionalOrElseThrow() {
    String str = null;
    Optional.ofNullable(str).orElseThrow(() -> new RuntimeException());
}

相當(dāng)于

@Test
public void testOptionalOrElseThrow() {
    String str = null;
    if (str == null) {
        throw new RuntimeException();
    }
}

轉(zhuǎn)換值 map()

map() 在工作中是使用比較多的,先來個使用 API 看看工作流程

@Test
public void testOptionalMap() {
    Student student = new Student("馬馬馬馬馬百萬");
    String studentName = Optional.ofNullable(student).map(Student::getName).orElse("-");
}

map() 是可能無限級聯(lián)的,像文章開始舉出的例子就可以使用 map() 來解決
如果在 map() 方法的調(diào)用鏈中任意一環(huán)節(jié)出現(xiàn)空的情況,直接走 orElse("-")

@Test
public void testOptionalMap() {
    String courseName = Optional.ofNullable(student)
            .map(Student::getSchoolClass)
            .map(SchoolClass::getCourse)
            .map(Course::getCourseName)
            .orElse("-");
}

轉(zhuǎn)換值 flatMap()

map()flatMap() 兩個函數(shù)作用上沒有什么區(qū)別,區(qū)別在于 map() 入?yún)⑹?Function<? super T, ? extends U>,flatMap() 入?yún)⑹?Function<? super T, Optional<U>>
如果說Student獲取SchoolClass的get方法是這樣的

public Optional<SchoolClass> getSchoolClass() {
        return Optional.ofNullable(schoolClass);
    }

那么在獲取時的代碼必須要是這種形式

String courseName = Optional.ofNullable(student)
        .flatMap(Student::getSchoolClass)
        .map(SchoolClass::getCourse)
        .map(Course::getCourseName).orElse("-");

過濾值 filter()

filter() 接受一個 Predicate 參數(shù),返回測試結(jié)果為 true 的值。如果測試結(jié)果為 false,會返回一個空的 Optional

比如有這么一個場景,我想查詢學(xué)生姓名為 “張三” 的一名學(xué)生,如果查詢不到返回 “-”

String studentName = Optional.ofNullable(student)
        .map(Student::getName)
        .filter(data -> Objects.equals(data, "張三"))
        .orElse("-");

使用樣例

場景?? I

檢查學(xué)生的個人介紹中是否包含 特殊字符,如果包含則拋出異常

Optional.ofNullable(student)
        .map(Student::getDetails)
        .filter(Student::isDetailsValid)
        .orElseThrow(() -> new RuntimeException());
    }
場景? II

如果學(xué)生學(xué)號 為空 執(zhí)行某一操作

Optional.ofNullable(student)
        .map(Student::getStuNum)
        .ifPresent(data -> xxxx(data));
場景? III

查看學(xué)生名字是否叫 百萬 ,不是默認(rèn)返回

Student student = Optional.ofNullable(student)
        .filter(data -> Objects.equals(data.getName(), "百萬"))
        .orElseGet(() -> new Student("百萬"));

參考文章

理解、學(xué)習(xí)與使用 Java 中的 Optional
JAVA8之妙用Optional解決NPE問題

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容