您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關在Java8中使用Optional需要注意哪些問題,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結了以下內容,希望大家根據這篇文章可以有所收獲。
前言
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 字段或方法參數時就變得復雜了,需要進行兩番檢查。
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(...)
方法時也有些累贅了
user.setMiddleName(Optional.empty()); user.setMiddleName(Optional.of("abc"));
而如果字段類型非 Optional 類型,而傳入的方法參數為 Optional 類型,要進行賦值的話
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 檢查,總有一種不安全感,步步驚心,結果可能事與愿違。
public User getUserById(String userId) { if(userId != null) { return userDao.findById(userId); } else { return null; } }
只是到了 Java 8 改成了用 Optional
return if(Optional.ofNullable(userId) .map(id -> userDao.findById(id)) .orElse(null);
上面兩段代碼其實是同樣的問題,如果輸入的 userId 是 null 值不調用 findById(...)
方法而直接返回 null 值,這就有兩個問題
userDao.findById(...) getUserById(userId)
這種情況下立即拋出 NullPointerException 是一個更好的主意,參考下面的代碼
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 拋出異常也不能明確異常造成的原因,比如下面的代碼
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 對象
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(...)
的功能,只要看下它的源代碼就知道
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(...)
函數
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(...)
方法,例如
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(..)
兩個方法。看看它們的源代碼:
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 值的字面常量
Optional.ofNullable(100); //這樣不好 Optional.of(100); //應該這么用
小結:
要理解 Optional 的設計用意,所以語意上應用它來表達有/無結果,不適于作為類字段與方法參數
傾向于方法返回單個對象,用 Optional 類型表示無結果以避免 null 值的二義性
Optional 進行方法參數檢查不能掩蓋了錯誤,最好是明確非法的參數輸入及時拋出輸入異常
對于最后兩種不正確的用法應熟悉 Optional 的源代碼實現就能規避
看完上述內容,你們對在Java8中使用Optional需要注意哪些問題有進一步的了解嗎?如果還想了解更多知識或者相關內容,請關注億速云行業資訊頻道,感謝大家的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。