簡介
optional類是java8中引入的針對NPE問題的一種優美處理方式,源碼作者也希望以此替代null。
歷史
1965年,英國一位名為Tony Hoare的計算機科學家在設計ALGOL W語言時提出了null引用的想法。Hoare選擇null引用這種方式,“只是因為這種方法實現起來非常容易”。很多年后,他開始為自己曾經做過這樣的決定而后悔不迭,把它稱為“我價值百萬的重大失誤”。我們已經看到它帶來的后果——程序員對對象的字段進行檢查,判斷它的值是否為期望的格式,最終卻發現我們查看的并不是一個對象,而是一個空指針,它會立即拋出一個讓人厭煩的NullPointerException異常[1]。
null帶來的種種問題
- 錯誤之源。
NullPointerException是目前Java程序開發中最典型的異常。
- 代碼膨脹。
它讓你的代碼充斥著深度嵌套的null檢查,代碼的可讀性糟糕透頂。
- 自身是毫無意義的。
null自身沒有任何的語義,尤其是,它代表的是在靜態類型語言中以一種錯誤的方式對缺失變量值的建模。
- 破壞了Java的哲學。
Java一直試圖避免讓程序員意識到指針的存在,唯一的例外是:null指針。
- 在Java的類型系統上開了個口子。
null并不屬于任何類型,這意味著它可以被賦值給任意引用類型的變量。這會導致問題,原因是當這個變量被傳遞到系統中的另一個部分后,你將無法獲知這個null變量最初的賦值到底是什么類型。
方案
汲取Haskell和Scala的靈感,Java 8中引入了一個新的類java.util.Optional<T>。這是一個封裝Optional值的類。舉例來說,使用新的類意味著,如果你知道一個人可能有學校也可能沒有,那么Student類內部的school變量就不應該聲明為Schoold,遭遇某學生沒有學校時把null引用賦值給它,而是應該像本篇那樣直接將其聲明為Optional<School>類型。
場景引入
首先我們引入一個常見的兩個場景
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
/** * >1 引入,常規判斷一個學生的學校是不是公立學校,判斷是否成年 */ public static boolean checkIsPublicV1(Student student) { if (student != null ) { School school = student.getSchool(); if (school != null ) { return school.isPublicFlag(); } } throw new RuntimeException( "參數異常" ); } public static String getAdultV1(Student student) { if (student != null ) { int age = student.getAge(); if (age > 18 ) { return student.getName(); } } return "無" ; } |
上述方式是我們常見的判讀流程,optional就是針對每次空判斷導致的代碼欣賞性問題進行了一套解決方案
方法說明
構造函數
- 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的構造器私有,不能直接創建,只能通過類中的其他靜態方法創建,optional構造器支持一個泛型入參,且改參數不能為空
創建Optional對象
- 聲明一個空的Optional: Optional<T> empty()
- 依據一個非空值創建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類中維護了一個null值的對象,使用empty靜態方法即可返回該空值對象
of(T var0)源碼
1
2
3
|
public static <T> Optional<T> of(T var0) { return new Optional(var0); } |
返回常見的有參構造對象,注意由于構造器要求入參不能為空,因此of方法的入參為空的話,依然會報NPE異常
ofNullable(T var0)源碼
1
2
3
|
public static <T> Optional<T> ofNullable(T var0) { return var0 == null ? empty() : of(var0); } |
這個方法是對of方法的補強,當ofNullable方法的入參不為空是正常返回構造對象,當入參為空時,返回一個空值的optional對象,而不會拋出異常。
ofNullable方法大部分場景優于of的使用。
null引用和Optional.empty()有什么本質的區別嗎?從語義上,你可以把它們當作一回事兒,但是實際中它們之間的差別非常大:如果你嘗試解引用一個null,一定會觸發NullPointerException,不過使用Optional.empty()就完全沒事兒,它是Op-tional類的一個有效對象,多種場景都能調用,非常有用。
舉例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public static void testOptionalBuild() { // 1. 無法直接new 'Optional()' has private access in 'java.util.Optional' // Optional<School> school = new Optional<>(); // 2. of構建非空和空對象(NullPointerException) /*Optional<String> o1 = Optional.of("test"); System.out.println(o1); Optional<Object> o2 = Optional.of(null); System.out.println(o2);*/ // 3. ofNullable構建非空和空對象(Optional.empty) /*Optional<String> o3 = Optional.ofNullable("test"); System.out.println(o3); Optional<Object> o4 = Optional.ofNullable(null); System.out.println(o4);*/ } |
使用map從Optional對象中提取和轉換值
- 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)); } |
當optional包裹的值為空時直接放回空對象,否則執行入參中的Function.apply方法
使用flatMap鏈接Optional對象
- 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幾乎一致。注意的是,入參的Function.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( "無名" ); String school2 = Optional.ofNullable(student2).map(i -> i.getName()).orElse( "無名" ); System.out.println( "school1: " + school1 + "| school2: " + school2); // flapMap 鏈式 String school3 = Optional.ofNullable(getOptionalStudent()).flatMap(i -> getOptionalStudent()).flatMap(i->i.getSchool()).map(i->i.getSchoolName()).orElse( "沒上大學" ); System.out.println( "school3: " + school3); } |
默認行為及解引用Optional對象1
- T orElse(T var1)
- T orElseGet(Supplier<? extends T> var1)
- T orElseThrow(Supplier<? extends X> var1)
注:這三個方法方法不是靜態方法,因此需要通過實例對象調用,一般跟在方法ofNullable后用于處理空值或返回值
源碼
orElse源碼
1
2
3
|
public T orElse(T var1) { return this .value != null ? this .value : var1; } |
當optional中的包裹值不為空時返回包裹的值,若為空則返回orElse中的入參值
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; } } |
與上個方法類似,當optional中的包裹值不為空時返回包裹的值,若為空執行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(); } } |
類似的,當optional中的包裹值不為空時返回包裹的值,若為空拋出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( "學生不存在" )); System.out.println(realStu3); } |
默認行為及解引用Optional對象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包裹的值不為空時,繼續處理入參中Consumer.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(); } } |
用于對optional對象的過濾,當optional包裹的值不為空時返回該值,否則執行filter入參的Predicate.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); } |
實戰
關于optional類的說明大致已經講完,再回到開始的時候,提到的場景引入,結合optional進行改造
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
/** * 實戰1 * 針對原來的checkIsPublicV1進行改造 */ public static boolean checkIsPublicV2(Student student) { return Optional.ofNullable(student).map(i -> i.getSchool()).map(i -> i.isPublicFlag()).orElseThrow(() -> new RuntimeException( "參數異常" )); } /** * 實戰1 * 針對原來的getAdultV1進行改造 */ public static String getAdultV2(Student student) { return Optional.ofNullable(student).filter(i->i.getAge()> 18 ).map(i->i.getName()).orElseGet(()->getDefaultStudent().getName()); } |
附:
補充代碼
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) { //逐個放開 // 引入 // System.out.println(checkIsPublicV1(stu2)); // System.out.println(getAdultV1(stu2)); // optional方法 // testOptionalBuild(); // testOptionalOrElse(); // testOptionalIfPresent(); // testOptionalMap(); // testOptionalFilter(); // 實戰 // System.out.println(getAdultV2(stu3)); // System.out.println(checkIsPublicV2(stu3)); } /**========模型數據=======**/ @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( "藍鯨大學" ).publicFlag( true ).build())).build()); } public static void acceptStudent(Student stu, LocalDate date) { System.out.println( "日期: " + date + " 新增一位學生: " + stu.getName()); } |
[1] 參考自java8實戰
詳細源碼,請參考:github.com/chetwhy/clo…
總結
到此這篇關于Java8中Optional類使用的文章就介紹到這了,更多相關Java8 Optional類使用內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!
原文鏈接:https://juejin.cn/post/7025925484462473246