摘要:本文已收錄修煉內功躍遷之路的為解決空的問題帶來了很多新思路,查看源碼,實現(xiàn)非常簡單,邏輯也并不復雜。
本文已收錄【修煉內功】躍遷之路
Java8的Optional為解決"空"的問題帶來了很多新思路,查看Optional源碼,實現(xiàn)非常簡單,邏輯也并不復雜。Stuart Marks在其一次演講中花了約1個小時的時間來講述如何正確的使用Optional (Optional - The Mother of All Bikesheds by Stuart Marks),也有人調侃道1 hour for Optional, you gotta be kidding me?.使用Optional不難,但用好Optional并不容易
Stuart Marks在演講中提到了Optional的基本作用
Optional is intended to provide a limited mechanism for library method return types where there is a clear need to represent "no result", and where using null for that is overwhelmingly likely to cause errors.
在以往的編程模型中,對于“沒有內容”,大多數情況需要使用null來表示,而null值總是被人忽略處理(判斷),從而在使用過程中極易引起NPE異常
Optional的出現(xiàn)并不是為了替代null,而是用來表示一個不可變的容器,它可以包含一個非null的T引用,也可以什么都不包含(不包含不等于null),非空的包含被稱作persent,而空則被稱作absent
本質上講Optional類似于異常檢查,它迫使API用戶去關注/處理Optional中是否包含內容,從而避免因為忽略null值檢查而導致的一些潛在隱患
假設有一個函數用來根據ID查詢學生信息public Student search(Long id),現(xiàn)在有一個需求,需要根據ID查詢學生姓名
public String searchName(Long id) { Student student = search(id); return student.getName(); }
注意,search函數是可能返回null的,在這種情況下searchName很有可能會拋出NPE異常
public String searchName(Long id) { Student student = search(id); return Objects.nonNull(student) ? student.getName() : "UNKNOWN"; }
除非特別明確函數的返回值不可能為null,否則一定要做null值檢查,雖然這樣寫并沒有增加太大的編碼負擔,但人總歸是懶惰的,忽略檢查的情況也總是會出現(xiàn)
如果我們改造search函數返回Optional,public Optional
public String searchName(Long id) { Optionalstudent = search(id); return student.getName(); }
這樣的代碼是編譯不過的,它會強制讓你去檢查search返回的值是否有內容
public String searchName(Long id) { Optionalstudent = search(id); return student.map(Student::getName).orElse("UNKNOWN"); }
Optional的使用可以參考其API文檔,以下內容假設您已了解如何使用Optional
但是否就應該消滅null,全部使用Optional來替代,回答當然是NO,null自有它的用武之地,Optional也并不是全能的
kotlin等語言,使用?.符號來解決java中if...else…或... ? ... : ...的啰嗦寫法,如上問題可以使用student?.name : null,其語義為"當studen不為null時取其name屬性值,否則取null值",kotlin的語法只是簡化了編程方式,讓編程變得更"爽",但并沒有解決"人們容易忽略null值檢查"的情況
Stuart Marks從5個不同的角度詳細講述了如何使用Optional,這里不一一敘述,有興趣的可以直接跳到視頻去看,下面將從Stuart Marks提到的7個Optional使用規(guī)范,來講述如何正確使用/不要濫用Optional,最后重點解釋一下【為什么Optional不能序列化】
0x00 使用規(guī)約 Rule 1: Never, ever, user null for an Optional variable or return value.Optional也是一個引用類型(reference type),其本身也可以賦值為null,如果我們在使用Optional的時候還要多做一層null檢查,就違背了Optional的設計初衷,所以在任何時候都不要將Optional類型的變量或返回值賦值為null,我們希望的是在遇到Optional的時候不需要關心其是否為null,只需要判斷其是否有值即可
public String searchName(Long id) { OptionalRule 2: Never user Optional.get() unless you can prove that the Optional is present.student = search(id); if (Objects.isNull(student)) { // Optional可能為null,這嚴重違背了Optional的設計初衷 return null; } return student.map(Student::getName).orElse("UNKNOWN"); }
如果Optional是absent(不包含任何值)的時候使用Optional.get(),會拋出NoSuchElementException異常,該接口的設計者也承認其設計的不合理,之后的某個jdk版本中可能會將其標記為@Deprecated,但還沒有計劃將其移除
public String searchName(Long id) { Optionalstudent = search(id); // 直接使用get(),可能會拋NoSuchElementException異常 return student.get().getName(); }
如果確實需要在Optional無內容的時候拋出異常,也請不要使用Optional.get()方法,而要使用Optional.getOrThrow()方法,主動指定需要拋出的異常,雖然該方法并未在jdk8中設計,但已經有計劃在接下來的jdk版本中加入
Rule 3: Prefer alternatives to Optional.isPresent() and Optional.get().如果一定要使用Optional.get(),請一定要配合isPresent(),先判斷Optional中是否有值
public String searchName(Long id) { OptionalRule 4: It"s generally a bad idea to create an Optional for the specific purpose of chaining methods from it to get a value.student = search(id); // 如果一定要使用Optional.get(),請一定要配合isPresent() return student.isPresent() ? student.get().getName() : "UNKNOWN"; }
鏈式語法可以讓代碼的處理流程看起來更加清晰,但是為了鏈式而去使用Optional的話,在某些情況下并不會顯得有多優(yōu)雅
比如,本來可以使用三目運算
String process(String s) { return Objects.nonNull(s) ? s : "DEFAULT"; }
如果非要硬生生地使用鏈式的話
String process(String s) { return Optional.ofNullable(s).orElse("DEFAULT"); }
比如,本來可以使用if判斷值的有效性
BigDecimal first = getFirstValue(); BigDecimal second = getSecondeValue(); if (Objects.nonNull(first) && Objects.nonNull(second)) { return first.add(second.get()); } else { return Objects.isNull(first) ? second : first; }
如果非要使用鏈式
Optionalfirst = getFirstValue(); Optional second = getSecondeValue(); return Stream.of(first, second) .filter(Optional::isPresent) .map(Optional::get) .reduce(BigDecimal::add);
或者
Optionalfirst = getFirstValue(); Optional second = getSecondeValue(); return first.map(b -> second.map(b::add).orElse(b)) .map(Optional::of) .orElse(second);
從可讀性及可維護性上來講并沒有提升,反而會帶來一絲閱讀困難,并且上文說過,Optional本身為引用類型,創(chuàng)建的Optional會進入堆內存,如果大量的不合理的使用Optional,也會在一定程度上影響JVM的堆內存及內存回收
Rule 5: If an Optional chain has a nested Optional chain, or has an intermediate result of Optional在使用Optional的時候,一定要保證Optional的簡潔性,即Optional運算過程中所包含的類型既是最終需要的類型值,不要出現(xiàn)Optional嵌套的情況
Optionalfirst = getFirstValue(); Optional second = getSecondeValue(); if (!first.isPresent && ! sencond.isPresent()) { return Optional.empty(); } else { return Optional.of(first.orElse(ZERO).add(second.orElse(ZERO))); }
這樣的寫法,會對代碼的閱讀帶來很大的困擾
Rule 6: Avoid using Optional in fields, method parameters, and collections.盡量避免將Optional用于類屬性、方法參數及集合元素中,因為以上三種情況,完全可以使用null值來代替Optional,沒有必要必須使用Optional,另外Optional本身為引用類型,大量使用Optional會出現(xiàn)類似(這樣描述不完全準確)封箱、拆箱的操作,在一定程度上會影響JVM的堆內存及內存回收
Rule 7: Avoid using identity-sensitive operations on Optionals.首先需要解釋,什么是identity-sensitive,可以參考 object identity and equality in java
identity-sensitive operations包含以下三種操作
reference equality,也就是 ==
identity hash code
synchronization
Optional的JavaDoc中有這樣一段描述
This is a value-based class; use of identity-sensitive operations (including reference equality(==), identity hash code, or synchronization) on instances of Optional may hava unpredictable results and should be avoided.
總結下來,就是要避免使用Optional的 == equals hashCode 方法
在繼續(xù)之前,需要再解釋一下什么是 value type
value type - Project Valhalla
an "object" that has no notion of identity
"code like a class, works like an int"
we eventually want to convert Optional into a value type
vale type,首先像一個類(至少從編碼角度來講),但是卻沒有類中identity的概念,運行的時候卻又和基本類型很相似
簡單來說就是編碼的時候像類,運行的時候像基本類型
顯然,Optional目前還不是value type,而是reference type,我們查看Optional類的equals及hashCode方法,并沒有發(fā)現(xiàn)有什么特別之處,但是有計劃在接下來的某個jdk版本中將Optional定義為value type
@Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof Optional)) { return false; } Optional> other = (Optional>) obj; return Objects.equals(value, other.value); } @Override public int hashCode() { return Objects.hashCode(value); }
在jdk8中使用Optional的identity-sensitive operations其實并沒有太大問題,但很難保證,在今后的后一個jdk版本中將Optional定義為value type時不會出問題,所以為了兼容jdk升級程序邏輯的正確性,請避免使用Optional的identity-sensitive operations
這也引出了Optional為什么不能序列化
0x01 序列化問題首先,需要了解jdk中序列化的一些背景
JDK rule: forward and backword serialization compatibility across releases
If Optional were serializable today, it would be serialized as an Object
it"all always be serialized as an Object, even if eventually becomes a value type
Serialization inherently depends on object identity
Consequencds of Optional being serializable
it might prevent it from being converted into a value type in the future
deserializing and Optional might result in a "boxed" value type
首先,JDK的序列化比較特殊,需要同時向前及向后兼容,如在JDK7中序列化的對象需要能夠在JDK8中反序列化,同樣在JDK8中序列化的對象需要能夠在JDK7中能夠反序列化
其次,序列化需要依賴于對象的identity
有了以上兩個序列化的前提條件,我們再來看Optional,上面已將說過,雖然目前Optional是reference type的,但其被標記為value based class,有計劃在今后的某一個JDK版本中將其實現(xiàn)為value type
如果Optional可以序列化,那現(xiàn)在就有兩個矛盾點
如果Optional可以序列化,那接下來的計劃中,就沒辦法將Optional實現(xiàn)為value type,而必須是reference type
或者將value type加入identity-sensitive operations,這對于目前所有已發(fā)行的JDK版本都是相沖突的
所以,雖然現(xiàn)在Optional是reference type,但有計劃將其實現(xiàn)為value type,考慮到JDK序列化的向前及向后兼容性,從一開始就將Optional定為不可序列化,應該是最合適的方案了
如果真的有在類屬性上使用Optional的需求怎么辦?這里有兩個替代方案/討論可以參考
Optional Pragmatic Approach
Nothing is better than the Optional type
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/74204.html
摘要:本文已收錄修煉內功躍遷之路初次接觸的時候感覺表達式很神奇表達式帶來的編程新思路,但又總感覺它就是匿名類或者內部類的語法糖而已,只是語法上更為簡潔罷了,如同以下的代碼匿名類內部類編譯后會產生三個文件雖然從使用效果來看,與匿名類或者內部類有相 本文已收錄【修煉內功】躍遷之路 showImg(https://segmentfault.com/img/bVbui4o?w=800&h=600)...
摘要:本文已收錄修煉內功躍遷之路我們寫的方法在被編譯為文件后是如何被虛擬機執(zhí)行的對于重寫或者重載的方法,是在編譯階段就確定具體方法的么如果不是,虛擬機在運行時又是如何確定具體方法的方法調用不等于方法執(zhí)行,一切方法調用在文件中都只是常量池中的符號引 本文已收錄【修煉內功】躍遷之路 showImg(https://segmentfault.com/img/bVbuesq?w=2114&h=12...
摘要:也正是因此,一旦出現(xiàn)內存泄漏或溢出問題,如果不了解的內存管理原理,那么將會對問題的排查帶來極大的困難。 本文已收錄【修煉內功】躍遷之路 showImg(https://segmentfault.com/img/bVbsP9I?w=1024&h=580); 不論做技術還是做業(yè)務,對于Java開發(fā)人員來講,理解JVM各種原理的重要性不必再多言 對于C/C++而言,可以輕易地操作任意地址的...
摘要:本文已收錄修煉內功躍遷之路學習語言的時候,需要在不同的目標操作系統(tǒng)上或者使用交叉編譯環(huán)境,使用正確的指令集編譯成對應操作系統(tǒng)可運行的執(zhí)行文件,才可以在相應的系統(tǒng)上運行,如果使用操作系統(tǒng)差異性的庫或者接口,還需要針對不同的系統(tǒng)做不同的處理宏的 本文已收錄【修煉內功】躍遷之路 showImg(https://segmentfault.com/img/bVbtpPd?w=2065&h=11...
大概一年多之前,我對java8的理解還僅限一些只言片語的文章之上,后來出于對函數式編程的興趣,買了本參考書看了一遍,然后放在了書架上,后來,當我接手大客戶應用的開發(fā)工作之后,java8的一些工具,對我的效率有了不小的提升,因此想記錄一下java8的一些常用場景,我希望這會成為一個小字典,能讓我免于頻繁翻書,但是總能找到自己想找的知識。 用于舉例的model: @Data public class ...
閱讀 3044·2021-11-22 09:34
閱讀 3636·2021-08-31 09:45
閱讀 3836·2019-08-30 13:57
閱讀 1670·2019-08-29 15:11
閱讀 1681·2019-08-28 18:04
閱讀 3218·2019-08-28 17:59
閱讀 1558·2019-08-26 13:35
閱讀 2188·2019-08-26 10:12