使用 Java8 有一段時間了,對于其中的 Optional 類使用較為頻繁,所以寫一篇文章記錄
我不會說是因為老記不住調(diào)用
Api才寫的
Optional 類主要解決的問題是 Java 常見的的空指針異常 NullPointerException
從創(chuàng)建 Optional 的 API 來看,可以創(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("百萬"));