【修煉內功】[Java8] 使用Optional的正確姿式及序列化問題

本文已收錄 【修煉內功】躍遷之路

clipboard.png

Java8的Optional爲解決'空'的問題帶來了不少新思路,查看Optional源碼,實現很是簡單,邏輯也並不複雜。Stuart Marks在其一次演講中花了約1個小時的時間來說述如何正確的使用Optional (Optional - The Mother of All Bikesheds by Stuart Marks),也有人調侃道1 hour for Optional, you gotta be kidding me.使用Optional不難,但用好Optional並不容易html

Stuart Marks在演講中提到了Optional的基本做用java

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的出現並非爲了替代null,而是用來表示一個不可變的容器,它能夠包含一個非null的T引用,也能夠什麼都不包含(不包含不等於null),非空的包含被稱做persent,而空則被稱做absentsegmentfault

本質上講Optional相似於異常檢查,它迫使API用戶去關注/處理Optional中是否包含內容,從而避免由於忽略null值檢查而致使的一些潛在隱患api

假設有一個函數用來根據ID查詢學生信息public Student search(Long id),如今有一個需求,須要根據ID查詢學生姓名oracle

public String searchName(Long id) {
    Student student = search(id);
    return student.getName();
}

注意,search函數是可能返回null的,在這種狀況下searchName頗有可能會拋出NPE異常app

public String searchName(Long id) {
    Student student = search(id);
    return Objects.nonNull(student) ? student.getName() : "UNKNOWN";
}

除非特別明確函數的返回值不可能爲null,不然必定要作null值檢查,雖然這樣寫並無增長太大的編碼負擔,但人總歸是懶惰的,忽略檢查的狀況也老是會出現less

若是咱們改造search函數返回Optional,public Optional<Student> search(Long id),再來重寫searchName函數ide

public String searchName(Long id) {
    Optional<Student> student = search(id);
    return student.getName();
}

這樣的代碼是編譯不過的,它會強制讓你去檢查search返回的值是否有內容函數

public String searchName(Long id) {
    Optional<Student> student = 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使用規範,來說述如何正確使用/不要濫用Optional,最後重點解釋一下【爲何Optional不能序列化

0x00 使用規約

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) {
    Optional<Student> student = search(id);
    if (Objects.isNull(student)) {
      // Optional可能爲null,這嚴重違背了Optional的設計初衷
      return null;
    }
    return student.map(Student::getName).orElse("UNKNOWN");
}

Rule 2: Never user Optional.get() unless you can prove that the Optional is present.

若是Optional是absent(不包含任何值)的時候使用Optional.get(),會拋出NoSuchElementException異常,該接口的設計者也認可其設計的不合理,以後的某個jdk版本中可能會將其標記爲@Deprecated,但尚未計劃將其移除

public String searchName(Long id) {
    Optional<Student> student = 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) {
    Optional<Student> student = search(id);
    // 若是必定要使用Optional.get(),請必定要配合isPresent()
    return student.isPresent() ? student.get().getName() : "UNKNOWN";
}

Rule 4: It's generally a bad idea to create an Optional for the specific purpose of chaining methods from it to get a value.

鏈式語法可讓代碼的處理流程看起來更加清晰,可是爲了鏈式而去使用Optional的話,在某些狀況下並不會顯得有多優雅

好比,原本可使用三目運算

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;
}

若是非要使用鏈式

Optional<BigDecimal> first = getFirstValue();
Optional<BigDecimal> second = getSecondeValue();
return Stream.of(first, second)
            .filter(Optional::isPresent)
            .map(Optional::get)
            .reduce(BigDecimal::add);

或者

Optional<BigDecimal> first = getFirstValue();
Optional<BigDecimal> second = getSecondeValue();
return first.map(b -> second.map(b::add).orElse(b))
            .map(Optional::of)
            .orElse(second);

從可讀性及可維護性上來說並無提高,反而會帶來一絲閱讀困難,而且上文說過,Optional自己爲引用類型,建立的Optional會進入堆內存,若是大量的不合理的使用Optional,也會在必定程度上影響JVM的堆內存及內存回收

Rule 5: If an Optional chain has a nested Optional chain, or has an intermediate result of Optional<Optional<T>>, it's probably too complex.

在使用Optional的時候,必定要保證Optional的簡潔性,即Optional運算過程當中所包含的類型既是最終須要的類型值,不要出現Optional嵌套的狀況

Optional<BigDecimal> first = getFirstValue();
Optional<BigDecimal> 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會出現相似(這樣描述不徹底準確)封箱、拆箱的操做,在必定程度上會影響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 方法

在繼續以前,須要再解釋一下什麼是 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類的equalshashCode方法,並無發現有什麼特別之處,可是有計劃在接下來的某個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版本中將其實現爲value type

若是Optional能夠序列化,那如今就有兩個矛盾點

  • 若是Optional能夠序列化,那接下來的計劃中,就沒辦法將Optional實現爲value type,而必須是reference type
  • 或者將value type加入identity-sensitive operations,這對於目前全部已發行的JDK版本都是相沖突的

因此,雖然如今Optional是reference type,但有計劃將其實現爲value type,考慮到JDK序列化的向前向後兼容性,從一開始就將Optional定爲不可序列化,應該是最合適的方案了

若是真的有在類屬性上使用Optional的需求怎麼辦?這裏有兩個替代方案/討論能夠參考


訂閱號

相關文章
相關標籤/搜索