Java中Optional的使用指南
提到NullPointerException(簡稱NPE)異常,相信每個Java開發人員都不陌生,從接觸編程的第1天起,它就和我們如影隨形,最近處理的線上bug中,有不少都是對象沒判空導致的NullPointerException異常。
1. 簡單回顧
引起NullPointerException異常的地方有很多,比如調用String的trim()方法,比如對BigDecimal進行計算時,比如將包裝類型轉化為基本類型時,這裡簡單回顧下。
假設有個導入模版定義如下:
package com.zwwhnly.springbootaction.model; import lombok.AllArgsConstructor; import lombok.Data; /** * 導入模版 */ @Data @AllArgsConstructor public class ImportTemplate { /** * 模版id */ private int templateId; /** * 模版名稱 */ private String templateName; /** * 模版下載url */ private String url; /** * 備註 */ private String remark; }
然後看下如下代碼:
public static void main(String[] args) { ImportTemplate importTemplate = getImportTemplateById(1); System.out.println(importTemplate.getUrl()); } public static ImportTemplate getImportTemplateById(int id) { return new ImportTemplate(1, "銷售訂單-普通商品導入模版", "o_w-140e3c1f41c94f238196539558e25bf7", null); }
正常情況下,這段代碼肯定是沒有問題的,但當getImportTemplateById方法返回null時,這段代碼就會拋出NullPointerException異常,如下所示:
public static ImportTemplate getImportTemplateById(int id) { return null; }
為瞭程序能正常運行,就要判斷importTemplate是否為null,所以代碼就修改為瞭:
public static void main(String[] args) { ImportTemplate importTemplate = getImportTemplateById(1); if (importTemplate != null) { System.out.println(importTemplate.getUrl()); } }
項目中類似的判空代碼應該有很多,大傢可以自行看下自己項目的代碼。
2. 使用Optional
為瞭避免NullPointerException異常,JDK1.8新增瞭Optional類來處理空指針異常,該類位於java.util包下,提供瞭一系列方法,
並且可以配合Lambda表達式一起使用,使代碼看起來更加清晰,接下來我們看下它的使用方法。
2.1 創建實例
創建Optional實例有以下3種方式,分別為:
調用empty方法
Optional<ImportTemplate> optionalImportTemplate = Optional.empty();
調用of方法
ImportTemplate importTemplate = new ImportTemplate(1, "銷售訂單-普通商品導入模版", "o_w-140e3c1f41c94f238196539558e25bf7", null); Optional<ImportTemplate> optionalImportTemplate = Optional.of(importTemplate);
調用ofNullable方法(推薦)
ImportTemplate importTemplate = new ImportTemplate(1, "銷售訂單-普通商品導入模版", "o_w-140e3c1f41c94f238196539558e25bf7", null); Optional<ImportTemplate> optionalImportTemplate = Optional.ofNullable(importTemplate);
值得註意的是,當參數為null時,調用of方法會拋NullPointerException異常,但調用ofNullable方法不會(更符合使用場景),因此推薦使用ofNullable方法:
ImportTemplate importTemplate = null; Optional<ImportTemplate> optionalImportTemplate = Optional.of(importTemplate);
2.2 判斷是否有值
可以調用isPresent
方法來判斷對象是否有值(不為null),使用方法如下所示:
ImportTemplate importTemplate = null; Optional<ImportTemplate> optionalImportTemplate = Optional.ofNullable(importTemplate); System.out.println(optionalImportTemplate.isPresent());
以上代碼的輸出結果為:
ImportTemplate importTemplate = new ImportTemplate(1, "銷售訂單-普通商品導入模版", "o_w-140e3c1f41c94f238196539558e25bf7", null); Optional<ImportTemplate> optionalImportTemplate = Optional.ofNullable(importTemplate); System.out.println(optionalImportTemplate.isPresent());
以上代碼的輸出結果為:
看下isPresent的源碼,邏輯非常簡單,就是判斷瞭我們傳入的對象是否有值,即不為null:
/** * Return {@code true} if there is a value present, otherwise {@code false}. * * @return {@code true} if there is a value present, otherwise {@code false} */ public boolean isPresent() { return value != null; }
2.3 獲取值
可以調用get方法來獲取對象的有值,使用方法如下所示:
ImportTemplate importTemplate = new ImportTemplate(1, "銷售訂單-普通商品導入模版", "o_w-140e3c1f41c94f238196539558e25bf7", null); Optional<ImportTemplate> optionalImportTemplate = Optional.ofNullable(importTemplate); System.out.println(optionalImportTemplate.get());
以上代碼的輸出結果為:
值得註意的是,當我們傳入的對象為null時,調用get方法會拋出java.util.NoSuchElementException
異常,而不是返回null。
ImportTemplate importTemplate = null; Optional<ImportTemplate> optionalImportTemplate = Optional.ofNullable(importTemplate); System.out.println(optionalImportTemplate.get());
以上代碼的輸出結果為:
看下get
方法的源碼,就可以知道原因:
public T get() { if (value == null) { throw new NoSuchElementException("No value present"); } return value; }
2.4 先用isPresent,再用get(不推薦)
然後我們回顧下文初的代碼:
ImportTemplate importTemplate = getImportTemplateById(1); if (importTemplate != null) { System.out.println(importTemplate.getUrl()); }
可能很多同學會把代碼優化為下面這樣的寫法:
Optional<ImportTemplate> optionalImportTemplate = Optional.ofNullable(getImportTemplateById(1)); if (optionalImportTemplate.isPresent()) { System.out.println(optionalImportTemplate.get().getUrl()); }
不推薦這麼使用,因為判斷的地方沒減少,而且還不如原來看起來清晰。
2.5 ifPresent(推薦)
那該怎麼優化呢?答案就是使用ifPresent
方法,該方法接收一個Consumer類型的參數,當值不為null時,就執行,當值為null時,就不執行,源碼如下所示:
public void ifPresent(Consumer<? super T> consumer) { if (value != null) consumer.accept(value); }
優化之後的代碼如下所示:
Optional<ImportTemplate> optionalImportTemplate = Optional.ofNullable(getImportTemplateById(1)); optionalImportTemplate.ifPresent(importTemplate -> System.out.println(importTemplate.getUrl()));
當然,也可以寫更多的邏輯:
Optional<ImportTemplate> optionalImportTemplate = Optional.ofNullable(getImportTemplateById(1)); optionalImportTemplate.ifPresent(importTemplate -> { System.out.println(importTemplate.getTemplateId()); System.out.println(importTemplate.getTemplateName()); System.out.println(importTemplate.getUrl()); System.out.println(importTemplate.getRemark()); });
2.6 自定義默認值
Optional類提供瞭以下2個方法來自定義默認值,用於當對象為null時,返回自定義的對象:
- orElse
- orElseGet
先來看下orElse方法的使用:
public static void main(String[] args) { ImportTemplate importTemplate = null; ImportTemplate firstImportTemplate = Optional.ofNullable(importTemplate) .orElse(getDefaultTemplate()); System.out.println(firstImportTemplate); importTemplate = new ImportTemplate(2, "銷售訂單-不定規格商品導入模版", "o_w-a7109db89f8d4508b4c6202889a1a2c1", null); ImportTemplate secondImportTemplate = Optional.ofNullable(importTemplate) .orElse(getDefaultTemplate()); System.out.println(secondImportTemplate); } public static ImportTemplate getDefaultTemplate() { System.out.println("getDefaultTemplate"); return new ImportTemplate(1, "銷售訂單-普通商品導入模版", "o_w-140e3c1f41c94f238196539558e25bf7", null); }
輸出結果:
再來看下orElseGet方法的使用:
public static void main(String[] args) { ImportTemplate importTemplate = null; ImportTemplate firstImportTemplate = Optional.ofNullable(importTemplate) .orElseGet(() -> getDefaultTemplate()); System.out.println(firstImportTemplate); importTemplate = new ImportTemplate(2, "銷售訂單-不定規格商品導入模版", "o_w-a7109db89f8d4508b4c6202889a1a2c1", null); ImportTemplate secondImportTemplate = Optional.ofNullable(importTemplate) .orElseGet(() -> getDefaultTemplate()); System.out.println(secondImportTemplate); } public static ImportTemplate getDefaultTemplate() { System.out.println("getDefaultTemplate"); return new ImportTemplate(1, "銷售訂單-普通商品導入模版", "o_w-140e3c1f41c94f238196539558e25bf7", null); }
輸出結果:
從輸出結果看,2個方法好像差不多,第1次調用都返回瞭默認模版,第2次調用都返回瞭傳入的模版,但其實仔細觀察,你會發現當使用
orElse方法時,getDefaultTemplate方法執行瞭2次,但調用orElseGet方法時,getDefaultTemplate方法隻執行瞭2次(隻在第1次傳入模版為null時執行瞭)。
為什麼會這樣呢?帶著這個疑問,我們看下這2個方法的源碼,其中orElse方法的源碼如下所示:
public T orElse(T other) { return value != null ? value : other; }
可以看到,參數other是個對象,這個參數肯定是要傳的,但隻有value為空時,才會用到(返回)這個對象。
orElseGet方法的源碼如下所示:
public T orElseGet(Supplier<? extends T> other) { return value != null ? value : other.get(); }
可以看到,參數other並不是直接傳入對象,如果value為null,才會執行傳入的參數獲取對象,如果不為null,直接返回value。
2.7 自定義異常
Optional類提供瞭orElseThrow方法,用於當傳入的對象為null時,拋出自定義的異常,使用方法如下所示:
public static void main(String[] args) { ImportTemplate importTemplate = new ImportTemplate(2, "銷售訂單-不定規格商品導入模版", "o_w-a7109db89f8d4508b4c6202889a1a2c1", null); ImportTemplate firstImportTemplate = Optional.ofNullable(importTemplate) .orElseThrow(() -> new IndexOutOfBoundsException()); System.out.println(firstImportTemplate); importTemplate = null; ImportTemplate secondImportTemplate = Optional.ofNullable(importTemplate) .orElseThrow(() -> new IndexOutOfBoundsException()); System.out.println(secondImportTemplate); }
輸出結果:
2.8 過濾數據
Optional類提供瞭filter方法來過濾數據,該方法接收一個Predicate參數,返回匹配條件的數據,如果不匹配條件,返回一個空的Optional,使用方法如下所示:
public static void main(String[] args) { ImportTemplate importTemplate = getImportTemplateById(1); Optional<ImportTemplate> filterById = Optional.ofNullable(importTemplate) .filter(f -> f.getTemplateId() == 1); System.out.println(filterById.isPresent()); Optional<ImportTemplate> filterByName = Optional.ofNullable(importTemplate) .filter(f -> f.getTemplateName().contains("發貨單")); System.out.println(filterByName.isPresent()); } public static ImportTemplate getImportTemplateById(int id) { return new ImportTemplate(1, "銷售訂單-普通商品導入模版", "o_w-140e3c1f41c94f238196539558e25bf7", null); }
輸出結果:
2.9 轉換值
Optional類提供瞭以下2個方法來轉換值:
- map
- flatMap
map方法的使用方法如下所示:
public static void main(String[] args) { ImportTemplate importTemplate = getImportTemplateById(1); Optional<String> optionalUrl = Optional.ofNullable(importTemplate) .map(f -> "url:" + f.getUrl()); System.out.println(optionalUrl.isPresent()); System.out.println(optionalUrl.get()); } public static ImportTemplate getImportTemplateById(int id) { return new ImportTemplate(1, "銷售訂單-普通商品導入模版", "o_w-140e3c1f41c94f238196539558e25bf7", null); }
輸出結果:
flatMap方法和map方法類似,不過它支持傳入Optional,使用方法如下所示:
public static void main(String[] args) { ImportTemplate importTemplate = getImportTemplateById(1); Optional<String> optionalUrl = Optional.ofNullable(importTemplate) .flatMap(f -> Optional.ofNullable(f.getUrl())); System.out.println(optionalUrl.isPresent()); System.out.println(optionalUrl.get()); } public static ImportTemplate getImportTemplateById(int id) { return new ImportTemplate(1, "銷售訂單-普通商品導入模版", "o_w-140e3c1f41c94f238196539558e25bf7", null); }
輸出結果:
3. 總結
對於程序員來說,一不註意就會出現NullPointerException異常,避免它的方式也很簡單,比如使用前判斷不能為空:
public static void main(String[] args) { ImportTemplate importTemplate = getImportTemplateById(1); if (importTemplate != null) { System.out.println(importTemplate.getUrl()); } }
比如為空時,直接返回(或者返回默認值):
public static void main(String[] args) { ImportTemplate importTemplate = getImportTemplateById(1); if (importTemplate == null) { return; } System.out.println(importTemplate.getUrl()); }
比如,使用本文中的Optional。
使用哪種方式不重要,盡可能地避免NullPointerException
異常才重要。
4. 參考
理解、學習與使用 Java 中的 Optional
總結
到此這篇關於Java中Optional使用的文章就介紹到這瞭,更多相關Java Optional使用內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Java8 Optional判空詳解(簡化判空操作)
- Java Optional的判空操作詳解
- Java JDK8新增Optional工具類講解
- Java8的Optional如何幹掉空指針(示例詳解)
- 一文教你搞定Java Optional類判空操作