簡(jiǎn)介
optional類是java8中引入的針對(duì)NPE問題的一種優(yōu)美處理方式,源碼作者也希望以此替代null。
歷史
1965年,英國(guó)一位名為Tony Hoare的計(jì)算機(jī)科學(xué)家在設(shè)計(jì)ALGOL W語(yǔ)言時(shí)提出了null引用的想法。Hoare選擇null引用這種方式,“只是因?yàn)檫@種方法實(shí)現(xiàn)起來(lái)非常容易”。很多年后,他開始為自己曾經(jīng)做過這樣的決定而后悔不迭,把它稱為“我價(jià)值百萬(wàn)的重大失誤”。我們已經(jīng)看到它帶來(lái)的后果——程序員對(duì)對(duì)象的字段進(jìn)行檢查,判斷它的值是否為期望的格式,最終卻發(fā)現(xiàn)我們查看的并不是一個(gè)對(duì)象,而是一個(gè)空指針,它會(huì)立即拋出一個(gè)讓人厭煩的NullPointerException異常[1]。
null帶來(lái)的種種問題
- 錯(cuò)誤之源。
NullPointerException是目前Java程序開發(fā)中最典型的異常。
- 代碼膨脹。
它讓你的代碼充斥著深度嵌套的null檢查,代碼的可讀性糟糕透頂。
- 自身是毫無(wú)意義的。
null自身沒有任何的語(yǔ)義,尤其是,它代表的是在靜態(tài)類型語(yǔ)言中以一種錯(cuò)誤的方式對(duì)缺失變量值的建模。
- 破壞了Java的哲學(xué)。
Java一直試圖避免讓程序員意識(shí)到指針的存在,唯一的例外是:null指針。
- 在Java的類型系統(tǒng)上開了個(gè)口子。
null并不屬于任何類型,這意味著它可以被賦值給任意引用類型的變量。這會(huì)導(dǎo)致問題,原因是當(dāng)這個(gè)變量被傳遞到系統(tǒng)中的另一個(gè)部分后,你將無(wú)法獲知這個(gè)null變量最初的賦值到底是什么類型。
方案
汲取Haskell和Scala的靈感,Java 8中引入了一個(gè)新的類java.util.Optional<T>。這是一個(gè)封裝Optional值的類。舉例來(lái)說(shuō),使用新的類意味著,如果你知道一個(gè)人可能有學(xué)校也可能沒有,那么Student類內(nèi)部的school變量就不應(yīng)該聲明為Schoold,遭遇某學(xué)生沒有學(xué)校時(shí)把null引用賦值給它,而是應(yīng)該像本篇那樣直接將其聲明為Optional<School>類型。
場(chǎng)景引入
首先我們引入一個(gè)常見的兩個(gè)場(chǎng)景
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
/** * >1 引入,常規(guī)判斷一個(gè)學(xué)生的學(xué)校是不是公立學(xué)校,判斷是否成年 */ public static boolean checkIsPublicV1(Student student) { if (student != null ) { School school = student.getSchool(); if (school != null ) { return school.isPublicFlag(); } } throw new RuntimeException( "參數(shù)異常" ); } public static String getAdultV1(Student student) { if (student != null ) { int age = student.getAge(); if (age > 18 ) { return student.getName(); } } return "無(wú)" ; } |
上述方式是我們常見的判讀流程,optional就是針對(duì)每次空判斷導(dǎo)致的代碼欣賞性問題進(jìn)行了一套解決方案
方法說(shuō)明
構(gòu)造函數(shù)
- Optional(T var1)
源碼
1
2
3
4
5
6
7
8
9
|
private final T value; private Optional() { this .value = null ; } private Optional(T var1) { this .value = Objects.requireNonNull(var1); } |
從源碼可知,optional的構(gòu)造器私有,不能直接創(chuàng)建,只能通過類中的其他靜態(tài)方法創(chuàng)建,optional構(gòu)造器支持一個(gè)泛型入?yún)?,且改參?shù)不能為空
創(chuàng)建Optional對(duì)象
- 聲明一個(gè)空的Optional: Optional<T> empty()
- 依據(jù)一個(gè)非空值創(chuàng)建Optional: Optional<T> of(T var0)
- 可接受null的Optional: Optional<T> ofNullable(T var0)
源碼
empty()源碼
1
2
3
4
5
6
7
8
9
10
|
private static final Optional<?> EMPTY = new Optional(); private Optional() { this .value = null ; } public static <T> Optional<T> empty() { Optional var0 = EMPTY; return var0; } |
從源碼可知,optional類中維護(hù)了一個(gè)null值的對(duì)象,使用empty靜態(tài)方法即可返回該空值對(duì)象
of(T var0)源碼
1
2
3
|
public static <T> Optional<T> of(T var0) { return new Optional(var0); } |
返回常見的有參構(gòu)造對(duì)象,注意由于構(gòu)造器要求入?yún)⒉荒転榭?,因此of方法的入?yún)榭盏脑?,依然?huì)報(bào)NPE異常
ofNullable(T var0)源碼
1
2
3
|
public static <T> Optional<T> ofNullable(T var0) { return var0 == null ? empty() : of(var0); } |
這個(gè)方法是對(duì)of方法的補(bǔ)強(qiáng),當(dāng)ofNullable方法的入?yún)⒉粸榭帐钦7祷貥?gòu)造對(duì)象,當(dāng)入?yún)榭諘r(shí),返回一個(gè)空值的optional對(duì)象,而不會(huì)拋出異常。
ofNullable方法大部分場(chǎng)景優(yōu)于of的使用。
null引用和Optional.empty()有什么本質(zhì)的區(qū)別嗎?從語(yǔ)義上,你可以把它們當(dāng)作一回事兒,但是實(shí)際中它們之間的差別非常大:如果你嘗試解引用一個(gè)null,一定會(huì)觸發(fā)NullPointerException,不過使用Optional.empty()就完全沒事兒,它是Op-tional類的一個(gè)有效對(duì)象,多種場(chǎng)景都能調(diào)用,非常有用。
舉例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public static void testOptionalBuild() { // 1. 無(wú)法直接new 'Optional()' has private access in 'java.util.Optional' // Optional<School> school = new Optional<>(); // 2. of構(gòu)建非空和空對(duì)象(NullPointerException) /*Optional<String> o1 = Optional.of("test"); System.out.println(o1); Optional<Object> o2 = Optional.of(null); System.out.println(o2);*/ // 3. ofNullable構(gòu)建非空和空對(duì)象(Optional.empty) /*Optional<String> o3 = Optional.ofNullable("test"); System.out.println(o3); Optional<Object> o4 = Optional.ofNullable(null); System.out.println(o4);*/ } |
使用map從Optional對(duì)象中提取和轉(zhuǎn)換值
- Optional<U> map(Function<? super T, ? extends U> var1)
源碼
1
2
3
4
|
public <U> Optional<U> map(Function<? super T, ? extends U> var1) { Objects.requireNonNull(var1); return ! this .isPresent() ? empty() : ofNullable(var1.apply( this .value)); } |
當(dāng)optional包裹的值為空時(shí)直接放回空對(duì)象,否則執(zhí)行入?yún)⒅械腇unction.apply方法
使用flatMap鏈接Optional對(duì)象
- Optional<U> flatMap(Function<? super T, Optional<U>> var1)
源碼
1
2
3
4
|
public <U> Optional<U> flatMap(Function<? super T, Optional<U>> var1) { Objects.requireNonNull(var1); return ! this .isPresent() ? empty() : (Optional)Objects.requireNonNull(var1.apply( this .value)); } |
與map幾乎一致。注意的是,入?yún)⒌腇unction.apply方法中,返回類型為optional類型
舉例
1
2
3
4
5
6
7
8
9
10
11
|
public static void testOptionalMap() { Student student1 = getDefaultStudent(); Student student2 = getBackStudent(); // map String school1 = Optional.ofNullable(student1).map(i -> i.getName()).orElse( "無(wú)名" ); String school2 = Optional.ofNullable(student2).map(i -> i.getName()).orElse( "無(wú)名" ); System.out.println( "school1: " + school1 + "| school2: " + school2); // flapMap 鏈?zhǔn)?/code> String school3 = Optional.ofNullable(getOptionalStudent()).flatMap(i -> getOptionalStudent()).flatMap(i->i.getSchool()).map(i->i.getSchoolName()).orElse( "沒上大學(xué)" ); System.out.println( "school3: " + school3); } |
默認(rèn)行為及解引用Optional對(duì)象1
- T orElse(T var1)
- T orElseGet(Supplier<? extends T> var1)
- T orElseThrow(Supplier<? extends X> var1)
注:這三個(gè)方法方法不是靜態(tài)方法,因此需要通過實(shí)例對(duì)象調(diào)用,一般跟在方法ofNullable后用于處理空值或返回值
源碼
orElse源碼
1
2
3
|
public T orElse(T var1) { return this .value != null ? this .value : var1; } |
當(dāng)optional中的包裹值不為空時(shí)返回包裹的值,若為空則返回orElse中的入?yún)⒅?/p>
orElseGet源碼
1
2
3
4
5
6
7
8
9
10
|
public T orElseGet(Supplier<? extends T> var1) { return this .value != null ? this .value : var1.get(); } public T get() { if ( this .value == null ) { throw new NoSuchElementException( "No value present" ); } else { return this .value; } } |
與上個(gè)方法類似,當(dāng)optional中的包裹值不為空時(shí)返回包裹的值,若為空?qǐng)?zhí)行orElseGet中的Supplier方法
orElseThrow源碼
1
2
3
4
5
6
7
|
public <X extends Throwable> T orElseThrow(Supplier<? extends X> var1) throws X { if ( this .value != null ) { return this .value; } else { throw (Throwable)var1.get(); } } |
類似的,當(dāng)optional中的包裹值不為空時(shí)返回包裹的值,若為空拋出orElseThrow中的Supplier.get的異常方法
舉例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public static void testOptionalOrElse() { // orElse Student stu = getDefaultStudent(); Student backStudent = getBackStudent(); Student realStu1 = Optional.ofNullable(stu).orElse(backStudent); System.out.println(realStu1); // orElseGet Student realStu2 = Optional.ofNullable(stu).orElseGet(()-> getBackStudent()); System.out.println(realStu2); // orElseGet Student realStu3 = Optional.ofNullable(stu).orElseThrow(()-> new RuntimeException( "學(xué)生不存在" )); System.out.println(realStu3); } |
默認(rèn)行為及解引用Optional對(duì)象2
- boolean isPresent()
- void ifPresent(Consumer<? super T> var1)
源碼
1
2
3
4
|
isPresent()源碼 public boolean isPresent() { return this .value != null ; } |
用戶判斷optional包裹的值是否為空,返回布爾值
ifPresent(Consumer var1)源碼
1
2
3
4
5
|
public void ifPresent(Consumer<? super T> var1) { if ( this .value != null ) { var1.accept( this .value); } } |
用戶處理optional包裹的值不為空時(shí),繼續(xù)處理入?yún)⒅蠧onsumer.accept的方法。類似于 if(var!=null) {do sth}
舉例
1
2
3
4
5
6
7
8
9
10
11
|
public static void testOptionalIfPresent() { // isPresent() Student student1 = getDefaultStudent(); Student student2 = getBackStudent(); boolean b1 = Optional.ofNullable(student1).isPresent(); boolean b2 = Optional.ofNullable(student2).isPresent(); System.out.println( "b1: " + b1 + "| b2: " + b2); // isPresent(Consumer) Optional.ofNullable(student2).ifPresent(i-> acceptStudent(i, LocalDate.now())); } |
使用filter剔除特定的值
- Optional filter(Predicate<? super T> var1)
源碼
1
2
3
4
5
6
7
8
|
public Optional<T> filter(Predicate<? super T> var1) { Objects.requireNonNull(var1); if (! this .isPresent()) { return this ; } else { return var1.test( this .value) ? this : empty(); } } |
用于對(duì)optional對(duì)象的過濾,當(dāng)optional包裹的值不為空時(shí)返回該值,否則執(zhí)行filter入?yún)⒌腜redicate.test方法
舉例
1
2
3
4
5
6
7
8
9
10
|
public static void testOptionalFilter() { Student student1 = getDefaultStudent(); Student student2 = getBackStudent(); System.out.println(student1); System.out.println(student2); Student student3 = Optional.ofNullable(student1).filter(i -> i.getAge() > 18 ).orElse(getBackStudent()); Student student4 = Optional.ofNullable(student2).filter(i -> i.getAge() > 18 ).orElse(getBackStudent()); System.out.println(student3); System.out.println(student4); } |
實(shí)戰(zhàn)
關(guān)于optional類的說(shuō)明大致已經(jīng)講完,再回到開始的時(shí)候,提到的場(chǎng)景引入,結(jié)合optional進(jìn)行改造
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
/** * 實(shí)戰(zhàn)1 * 針對(duì)原來(lái)的checkIsPublicV1進(jìn)行改造 */ public static boolean checkIsPublicV2(Student student) { return Optional.ofNullable(student).map(i -> i.getSchool()).map(i -> i.isPublicFlag()).orElseThrow(() -> new RuntimeException( "參數(shù)異常" )); } /** * 實(shí)戰(zhàn)1 * 針對(duì)原來(lái)的getAdultV1進(jìn)行改造 */ public static String getAdultV2(Student student) { return Optional.ofNullable(student).filter(i->i.getAge()> 18 ).map(i->i.getName()).orElseGet(()->getDefaultStudent().getName()); } |
附:
補(bǔ)充代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
public static void main(String[] args) { //逐個(gè)放開 // 引入 // System.out.println(checkIsPublicV1(stu2)); // System.out.println(getAdultV1(stu2)); // optional方法 // testOptionalBuild(); // testOptionalOrElse(); // testOptionalIfPresent(); // testOptionalMap(); // testOptionalFilter(); // 實(shí)戰(zhàn) // System.out.println(getAdultV2(stu3)); // System.out.println(checkIsPublicV2(stu3)); } /**========模型數(shù)據(jù)=======**/ @Data @Builder @NoArgsConstructor @AllArgsConstructor static class Student { private String name; private int age; private School school; } @Data @Builder @NoArgsConstructor @AllArgsConstructor static class School { private String schoolName; private boolean publicFlag; } @Data @Builder @NoArgsConstructor @AllArgsConstructor static class StudentOpt { private String name; private int age; private Optional<School> school; } public static Student getDefaultStudent() { return null ; } public static Student getBackStudent() { return Student.builder().name( "小紅" ).age( 19 ).build(); } public static Optional<StudentOpt> getOptionalStudent() { return Optional.ofNullable(StudentOpt.builder().name( "小莫" ).age( 18 ) .school(Optional.ofNullable(School.builder().schoolName( "藍(lán)鯨大學(xué)" ).publicFlag( true ).build())).build()); } public static void acceptStudent(Student stu, LocalDate date) { System.out.println( "日期: " + date + " 新增一位學(xué)生: " + stu.getName()); } |
[1] 參考自java8實(shí)戰(zhàn)
詳細(xì)源碼,請(qǐng)參考:github.com/chetwhy/clo…
總結(jié)
到此這篇關(guān)于Java8中Optional類使用的文章就介紹到這了,更多相關(guān)Java8 Optional類使用內(nèi)容請(qǐng)搜索服務(wù)器之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持服務(wù)器之家!
原文鏈接:https://juejin.cn/post/7025925484462473246