前言
java 8 引入的 optional 類型,基本是把它當作 null 值優雅的處理方式。其實也不完全如此,optional 在語義上更能體現有還是沒有值。所以它不是設計來作為 null 的替代品,如果方法返回 null 值表達了二義性,沒有結果或是執行中出現異常。
在 oracle 做 java 語言工作的 brian goetz 在 stack overflow 回復 should java 8 getters return optional type? 中講述了引入 optional 的主要動機。
our intention was to provide a limited mechanism for library method return types where there needed to be a clear way to represent “no result”, and using null for such was overwhelmingly likely to cause errors.
說的是 optional 提供了一個有限的機制讓類庫方法返回值清晰的表達有與沒有值,避免很多時候 null 造成的錯誤。并非有了 optional 就要完全杜絕 nullpointerexception。
在 java 8 之前一個實踐是方法返回集合或數組時,應返回空集合或數組表示沒有元素; 而對于返回對象,只能用 null 來表示不存在,現在可以用 optional 來表示這個意義。
自 java8 于 2014-03-18 發布后已 5 年有余,這里就列舉幾個我們在項目實踐中使用 optional 常見的幾個用法。
optional 類型作為字段或方法參數
這兒把 optional 類型用為字段(類或實例變量)和方法參數放在一起來講,是因為假如我們使用 intellij idea 來寫 java 8 代碼,idea 對于 optional 作為字段和方法參數會給出同樣的代碼建議:
reports any uses of java.util.optional<t> , java.util.optionaldouble , java.util.optionalint , java.util.optionallong or com.google.common.base.optional as the type for a field or parameter. optional was designed to provide a limited mechanism for library method return types where there needed to be a clear way to represent "no result". using a field with type java.util.optional is also problematic if the class needs to be serializable , which java.util.optional is not.
不建議用任何的 optional 類型作為字段或參數,optional 設計為有限的機制讓類庫方法返回值清晰的表達 "沒有值"。 optional 是不可被序列化的,如果類是可序列化的就會出問題。
上面其實重復了 java 8 引入 optional 的意圖,我們還有必要繼續深入理解一下為什么不該用 optional 作為字段或方法參數。
當我們選擇 optional 類型而非內部包裝的類型后,應該是假定了該 optional 類型不為 null,否則我們在使用 optional 字段或方法參數時就變得復雜了,需要進行兩番檢查。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public class user { private string firstname; private optional<string> middlename = optional.empty(); private string lastname; public void setmiddlename(optional<string> middlename) { this .middlename = middlename; } public string getfullname() { string fullname = firstname; if (middlename != null ) { if (middlename.ispresent()){ fullname = fullname.concat( "." + middlename.get()); } return fullname.concat( "." + lastname); } } |
由于 middlename 的 setter 方法,我們可能造成 middlename 變為 null 值,所以在構建 fullname 時必須兩重檢查。
并且在調用 setmiddlename(...)
方法時也有些累贅了
1
2
|
user.setmiddlename(optional.empty()); user.setmiddlename(optional.of( "abc" )); |
而如果字段類型非 optional 類型,而傳入的方法參數為 optional 類型,要進行賦值的話
1
2
3
4
5
6
7
8
9
|
private string middlename; public void updatemiddlename(optional<string> middlename) { if (middlename != null ) { this .middlename = middlename.orelse( null ); } else { this .middlename = null ; } } |
前面兩段代碼如果應用 optional.ofnullable(...)
包裹 optional 來替代 if(middlename != null)
就更復雜了。
對于本例直接用 string 類型的 middlename 作為字段或方法參數就行,null 值可以表達沒有 middlename。如果不允許 null 值 middlename, 顯式的進行入口參數檢查而拒絕該輸入 -- 拋出異常。
利用 optional 過度檢查方法參數
這一 optional 的用法與之前的可能為 null 值的方法參數,不分清紅皂白就用 if...else 檢查,總有一種不安全感,步步驚心,結果可能事與愿違。
1
2
3
4
5
6
7
|
public user getuserbyid(string userid) { if (userid != null ) { return userdao.findbyid(userid); } else { return null ; } } |
只是到了 java 8 改成了用 optional
1
2
3
|
return if (optional.ofnullable(userid) .map(id -> userdao.findbyid(id)) .orelse( null ); |
上面兩段代碼其實是同樣的問題,如果輸入的 userid 是 null 值不調用 findbyid(...)
方法而直接返回 null 值,這就有兩個問題
1
2
|
userdao.findbyid(...) getuserbyid(userid) |
這種情況下立即拋出 nullpointerexception 是一個更好的主意,參考下面的代碼
1
2
3
4
5
6
7
8
9
|
public user getuserbyid(string userid) { //拋出出 nullpointerexception 如果 null userid return userdao.findbyid(objects.requirenonull(userid, "invalid null userid" ); } //or public user getuserbyid(string userid) { //拋出 illegalargumentexception 如果 null userid preconditions.checkargument(userid != null , "invalid null userid" ); return userdao.findbyid(userid); } |
即使用了 optional 的 orelsethrow 拋出異常也不能明確異常造成的原因,比如下面的代碼
1
2
3
4
5
6
|
public user getuserbyid(string userid) { return optional.ofnullable(userid) .map(id -> userdao.findbyid(id)) orelsethrow(() -> new runtimeexception( "userid 是 null 或 findbyid(id) 返回了 null 值" )); } |
糾正辦法是認真的審視方法的輸入參數,對不符合要求的輸入應立即拒絕,防止對下層的壓力與污染,并報告出準確的錯誤信息,以有利于快速定位修復。
optional.map(...) 中再次 null 值判斷
假如有這樣的對象導航關系 user.getorder().getproduct().getid()
, 輸入是一個 user 對象
1
2
3
4
5
|
string productid = optional.ofnullable(user) .map(user::getorder) .flatmap(order -> optional.ofnullable(order.getproduct())) //1 .flatmap(product -> optional.ofnullable(product.getid())) //2 .orelse( "" ); |
#1 和 #2 中應用 flatmap 再次用 optional.ofnullable()
是因為擔心 order.getproduct()
或 product.getid()
返回了 null 值,所以又用 optional.ofnullable(...)
包裹了一次。代碼的執行結果仍然是對的,代碼真要這么寫的話真是 oracle 的責任。這忽略了 optional.map(...)
的功能,只要看下它的源代碼就知道
1
2
3
4
5
6
7
8
|
public <u> optional<u> map(function<? super t, ? extends u> mapper) { objects.requirenonnull(mapper); if (!ispresent()) return empty(); else { return optional.ofnullable(mapper.apply(value)); } } |
map(...)
函數中已有考慮拆解后的 null 值,因此呢 flatmap 中又 optional.ofnullable
是多余的,只需簡單一路用 map(...)
函數
1
2
3
4
5
|
string productid = optional.ofnullable(user) .map(user::getorder) .map(order -> order.getproduct()) //1 .map(product -> product.getid()) //2 .orelse( "" ); |
optional.ofnullable 應用于明確的非 null 值
如果有時候只需要對一個明確不為 null 的值進行 optional 包裝的話,就沒有必要用 ofnullable(...)
方法,例如
1
2
3
4
5
6
7
8
|
public optional<user> getuserbyid(string userid) { if ( "admin" .equals(userid)) { user adminuser = new user( "admin" ); return optional.ofnullable(adminuser); //1 } else { return userdao.findbyid(userid); } } |
在代碼 #1 處非常明確 adminuser 是不可能為 null 值的,所以應該直接用 optional.of(adminuser)
。這也是為什么 optional 要聲明 of(..)
和 ofnullable(..)
兩個方法。看看它們的源代碼:
1
2
3
4
5
6
7
|
public static <t> optional<t> of(t value) { return new optional<>(value); } public static <t> optional<t> ofnullable(t value) { return value == null ? empty() : of(value); } |
知道被包裹的值不可能為 null 時調用 ofnullable(value)
多了一次多余的 null 值檢查。相應的對于非 null 值的字面常量
1
2
|
optional.ofnullable( 100 ); //這樣不好 optional.of( 100 ); //應該這么用 |
小結:
- 要理解 optional 的設計用意,所以語意上應用它來表達有/無結果,不適于作為類字段與方法參數
- 傾向于方法返回單個對象,用 optional 類型表示無結果以避免 null 值的二義性
- optional 進行方法參數檢查不能掩蓋了錯誤,最好是明確非法的參數輸入及時拋出輸入異常
- 對于最后兩種不正確的用法應熟悉 optional 的源代碼實現就能規避
鏈接:
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。
原文鏈接:https://yanbin.blog/java8-optional-several-common-incorrect-usages/