使用Optional擺脫NPE的折磨

来源:https://www.cnblogs.com/javaadu/archive/2019/07/16/11192604.html
-Advertisement-
Play Games

在目前的工作中,我對Java中的Stream和Lambda表達式都使用得很多,之前也寫了兩篇文章來總結對應的知識。 "024:Java流實現Shell:cat 1.log | grep a | sort | uniq c | sort rn" "函數式編程讓你忘記設計模式" 不過對於Optional ...


architectural-architectural-design-architecture

在目前的工作中,我對Java中的Stream和Lambda表達式都使用得很多,之前也寫了兩篇文章來總結對應的知識。

不過對於Optional這個特性,一直沒有很好地使用起來,所以最近又開始閱讀《Java 8實戰》這本書,本文是針對其中第10章的一個學習總結。

背景

在Java中,如果你嘗試對null做函數調用,就會引發NullPointerException(NPE),NPE是Java程式開發中的最典型的異常,對於Java開發者來說,無論你是初出茅廬的新人和還工作多年的老司機,NPE經常讓他們翻車。為了避免NPE,他們會加很多if判斷語句,使得代碼的可讀性變得很差。

從軟體設計的角度來看,null本身是沒有意義的語義,這是一種對缺失變數值的錯誤的建模。

從Java類型系統的角度看,null可以被賦值給任何類型的變數,並且不斷被傳遞,知道最後誰也不知道它是從哪裡引入的。

Optional的引入

Java設計者從Haskell和Scala中獲取靈感,在Java 8中引入了一個新的類java.util.Optional<T>。如果一個介面返回Optional,可以表示一個人可能有車也可能沒有車,這個比簡單的返回Car要更明確,閱讀代碼的人不需要提前準備業務知識。

Optional的目的就在於此:通過類型系統讓你的領域模型中隱藏的知識顯式地體現在你的代碼中。

Optional的使用

方法 描述
empty 返回一個空的Optional實例
filter 如果值存在並且滿足提供的過濾條件,則返回包含該值的Optional對象;否則就返回一個空的Optional對象
map 如果值存在,就對該值執行提供的mapping函數調用
flatMap 如果值存在,就對該值執行提供的mapping函數調用,返回一個Optional類型的值,否則就返回一個空的Optional對象
ifPresent 如果值存在,就執行使用該值的方法調用,否則什麼也不做
of 將指定值用Optional封裝之後返回,如果該值為null,則拋出一個NPE
ofNullable 將指定值用Optional封裝之後返回,如果該值為null,則返回一個空的Optional對象
orElse 如果有值則返回,否則返回一個預設值
orElseGet 如果有值則返回,否則返回一個由指定的Supplier介面生成的值(如果預設值的生成代價比較高的話,則適合使用orElseGet方法)
orElseThrow 如果有值則返回,否則返回一個由指定的Supplier介面拋出的異常
get 如果值存在,則返回該值,否則拋出一個NoSuchElementException異常
isPresent 如果值存在則返回true,否則返回false

上面這張表裡列舉了Optional的基礎API,我這裡列舉了一些使用的tips:

  • 你可以用ofNullable將一個可能為null的對象封裝為Optional對象,然後獲取值的時候使用orElse方法提供預設值;可以使用empty方法創建一個空的Optional對象;of方法一般不用,不過如果你知道某個值不可能為null,則可以用Optional封裝該值,這樣它一旦為null就會拋出異常。
//empty方法的使用
Optional<Car> optCar = Optional.empty();

//of方法的使用
Optional<Car> optCar = Optional.of(car);

//ofNullable方法的使用
Optional<Car> optCar = Optional.ofNullable(car);
  • 你可以使用map方法從Optional對象中它封裝的值中的某個欄位的值;
Optional<Insurance> optInsurance = Optional.ofNullable(insurance);
Optional<String> name = optInsurance.map(Insurance::getName); 
  • 如果需要連續、層層遞進的從某個對象鏈的末端獲取欄位的值,則不能全部使用map方法,需要先使用flatMap,最後再使用map方法;
//轉換之前
public String getCarInsuranceName(Person person) {
  return person.getCar().getInsurance().getName();
}

//轉換後
public String getCarInsuranceName(Optional<Person> person) {
  return person.flatMap(Person::getCar)
               .flatMap(Car::Insurance)
               .map(Insurance::getName)
               .orElse("Unknown");
}
  • Optional中的map、flatMap和filter方法,在概念是與Stream中對應的方法都很類似,區別就在於Optional中的元素至多有一個,算是Stream的一種特殊情況——一種特殊的集合。
  • 不要使用ifPresent和get方法,它們本質上和不適用Optional對象之前的模式相同,都是臃腫的if-then-else判斷語句;
  • 由於Optional無法序列化,所以在領域模型中,無法將某個欄位定義為Optional的,原因是:Optional的設計初衷僅僅是要支持能返回Optional對象的語法,如果我們希望在域模型中引入Optional,則可以用下麵這種替代的方法:
public class Person {
  private Car car;
  public Optional<Car> getCarAsOptional() {
    return Optional.ofNullable(car);
  }
}
  • 不要使用基礎類型的Optional對象,原因是:基礎類型的Optional對象不支持map、flatMap和filter方法,而這些方法是Optional中非常強大的方法。

實戰案例

案例1:使用工具類方法改良可能拋出異常的API

Java方法處理異常結果的方式有兩種:返回null(或錯誤碼);拋出異常,例如:Integer.parseInt(String)這個方法——如果無法解析到對應的整型,該方法就拋出一個NumberFormationException,這種情況下我們一般會使用try/catch語句處理異常情況。

一般我們建議將try/catch塊單獨提取到一個方法中,在這裡使用Optional設計這個方法,代碼如下。在開發中,可以嘗試構建一個OptionalUtility工具類,將這些複雜的try/catch邏輯封裝起來。

public static Optional<Integer> stringToInt(String a) {
  try{
    return Optional.of(Integer.parseInt(s));
  } catch  (NumberFormationException e) {
    return Optional.empty();
  }
}

案例2:綜合案例

現在有個方法,是嘗試從一個屬性映射中獲取某個關鍵詞對應的值,例子代碼如下:

   public static int readDuration(Properties properties, String name) {
        String value = properties.getProperty(name);
        if (value != null) {
            try {
                int i = Integer.parseInt(value);
                if (i > 0) {
                    return i;
                }
            } catch (NumberFormatException e) {

            }
        }
        return 0;
    }

使用Optional的寫法後,代碼如下所示:

    public static int readDurationWithOptional(Properties properties, String name) {
        return Optional.ofNullable(properties.getProperty(name))
            .flatMap(OptionalUtility::stringToInt)
            .filter(integer -> integer > 0)
            .orElse(0);
    }

如果需要訪問的屬性值不存在,Properites.getProperty(String)方法的返回值就是一個null,使用noNullable工廠方法就可以將該值轉換為Optional對象;接下來,可以使用flatMap將一個Optional轉換為Optional對象;最後使用filter過濾掉負數,然後就可以使用orElse獲取屬性值,如果拿不到則返回預設值0。

總結

使用Optional的思路和Stream相同,都是鏈式思路,跟資料庫查詢似的,表達力很強,而且省去了哪些複雜的try/catch和if-then-else方法。在後面的開發中,可以使用Optional設計API,這樣可以設計出更安全的介面和方法。


本號專註於後端技術、JVM問題排查和優化、Java面試題、個人成長和自我管理等主題,為讀者提供一線開發者的工作和成長經驗,期待你能在這裡有所收穫。javaadu


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 來自:Python編程與實戰(微信號:pthon1024),作者:Jerryning 沒有辦法轉,整個複製下來了 本文要點 字元串拼接 拆分含有多種分隔符的字元串 判讀字元串a是否以字元串b開頭或結尾 調整字元串中文本的格式 對字元串進行左,右,居中對齊 刪除字元串中不需要的字元 字元串拼接 拆分含 ...
  • Spring Cloud Alibaba | 序言 @[TOC] 1. Spring Cloud Alibaba是什麼? Spring Cloud Alibaba 致力於提供微服務開發的一站式解決方案。此項目包含開發分散式應用微服務的必需組件,方便開發者通過 Spring Cloud 編程模型輕鬆使 ...
  • 我們分析軟體項目開發過程中各階段的時間占比可以慢慢體會到,一套軟體項目 UI及基本業務的設計會占到整個項目開發的50%以上的時間,再看UI設計,通用的部分占到70%以上,我們會想,如果能將這些重覆簡易的工作能否通過簡易的操作來快速實現呢? 這樣可以將大部分的時間用到後續的真實業務邏輯處理上去,就可以 ...
  • 接著學習設計模式系列,今天講解的是代理模式。 定義 什麼是代理模式? 代理模式,也叫委托模式,其定義是給某一個對象提供一個代理對象,並由代理對象控制對原對象的引用。它包含了三個角色: Subject:抽象主題角色。可以是抽象類也可以是介面,是一個最普通的業務類型定義。 RealSubject:具體主 ...
  • 《Head First設計模式》已經讀了不止一遍,但是始終沒有進行系統的進行總結。所以近期開始總結設計模式相關的知識,從模板方法模式開始,因為是一個我認為是最簡單的設計模式。(推薦視頻資源 "23個設計模式" ) 提出&解決問題 提出問題 實現製作咖啡功能 。且製作咖啡需要四個步驟 : 1. 燒水 ...
  • 舉個慄子 問題描述 小時候數學老師的隨堂測驗,都是在黑板上抄題目,然後再作答案。寫一個抄題目的程式。 簡單實現 學生甲抄的試卷 學生乙抄的試卷 測試 測試結果 存在問題 兩個學生抄試卷都非常類似,除了答案不同,沒什麼不一樣,這樣又容易錯,又難以維護。因此老師出一份試卷,列印多份,讓學生填寫答案就可以 ...
  • 架構雜談《三》 一致性問題 前面的《架構雜談一》和《架構雜談二》 雜談了從服務化到微服務架構的演進,並肯定了服務化和微服務架構是一脈相承的。微服務在服務化架構的基礎上,對服務化的細節和方案進行了優化和細化,重點突出了無中心化管理的微服務架構,通過對服務進行有效的拆分來實現敏捷開發和自動化部署,併在海 ...
  • SpringCloud系列教程 | 第十四篇:Spring Cloud Gateway高級應用 Springboot: 2.1.6.RELEASE SpringCloud: Greenwich.SR1 如無特殊說明,本系列教程全採用以上版本 上一篇我們聊了Gateway和註冊中心的使用,以及 Gat ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...