Optional原始碼解析與實踐

京東雲發表於2022-09-29

1 導讀

NullPointerException在開發過程中經常遇到,稍有不慎小BUG就出現了,如果避免這個問題呢,Optional就是專門解決這個問題的類,那麼Optional如何使用呢?讓我們一起探索一下吧!

2 原始碼解析

2.1 Optional定義

Optional類是Java8為了解決null值判斷問題而建立的容器類,在java.util 下,使用Optional類可以避免顯式的null值判斷,避免null導致的NullPointerException。首先,Optional是一個容器,它可以儲存型別T的值,也可以為null的容器物件。Optional容器只能存一個值。

2.2 Optional的屬性

1)原始碼:

/**
 * Common instance for {@code
private static final Optional<?> EMPTY = new Optional<>();


/**
 * If non-null, the value; if null, indicates no value is present
 */
private final T value;

根據原始碼可以看到Optional有兩個屬性,一個是為空值準備的EMPTY和泛型值value;

2.3 Optional的方法

Optional除toString()、hashCode() 、equals()等Object的方法外,還包含以下方法。

2.3.1 私有構造方法

/**
 * Constructs an empty instance.
 *
 * @implNote Generally only one empty instance, {@link Optional#EMPTY},
 * should exist per VM.
 */
private Optional() {
    this.value = null;
}


/**
* Constructs an instance with the value present.
*
* @param value the non-null value to be present
* @throws NullPointerException if value is null
*/
private Optional(T value) {
    this.value = Objects.requireNonNull(value);
}

分別是建立一個空例項和構造一個具有當前值的例項。

2.3.2 建立方法

1)原始碼

public static<T> Optional<T> empty() {
    @SuppressWarnings("unchecked")
    Optional<T> t = (Optional<T>) EMPTY;
    return t;
}
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);
}

2)方法說明

  • empty(): 建立一個空的 Optional 例項
  • of(T t) : 建立一個 Optional 例項,當 t為null時丟擲異常
  • ofNullable(T t): 建立一個 Optional 例項,但當 t為null時不會丟擲異常,而是返回一個空的例項

3)測試程式碼

public static void main(String[] args) {
    Integer value1 = null;
    Integer value2 = 1;
    try {
        Optional<Integer> optional1 = Optional.empty();
        System.out.println("optional1建立了");
    }catch (Exception e){
        System.out.println("optional1失敗了");
    }
    try {
        Optional<Integer> optional2 = Optional.of(value1);
        System.out.println("optional2建立了");
    }catch (Exception e){
        System.out.println("optional2失敗了");
    }
    try {
        Optional<Integer> optional3 = Optional.ofNullable(value1);
        System.out.println("optional3建立了");
    }catch (Exception e){
        System.out.println("optional3失敗了");
    }
    try {
        Optional<Integer> optional4 = Optional.of(value2);
        System.out.println("optional4建立了");
    }catch (Exception e){
        System.out.println("optional4失敗了");
    }
    try {
        Optional<Integer> optional5 = Optional.ofNullable(value2);
        System.out.println("optional5建立了");
    }catch (Exception e){
        System.out.println("optional5失敗了");
    }
}

4)執行結果

Optional原始碼解析與實踐_NullPointerException


2.3.3 值獲取方法

1)原始碼

public T get() {
    if (value == null) {
        throw new NoSuchElementException("No value present");
    }
    return value;
}

2)方法說明

get(): 如果Optional不為空,則返回該Optional容器中的值,否則丟擲NoSuchElementExceptio 。

3)測試程式碼

public static void main(String[] args) {
    Integer value1 = null;
    Integer value2 = 1;
    Optional<Integer> optional1 = Optional.ofNullable(value1);
    Optional<Integer> optional2 = Optional.of(value2);
    try {
        Integer result=optional1.get();
        System.out.println("optional1的值是:"+result);
    }catch (Exception e){
        System.out.println("optional1的值獲取失敗,原因:"+e.getMessage());
    }
    try {
        Integer result=optional2.get();
        System.out.println("optional2的值是:"+result);
    }catch (Exception e){
        System.out.println("optional2的值獲取失敗,原因:"+e.getMessage());
    }
}

4)執行結果

Optional原始碼解析與實踐_丟擲異常_02


2.3.4 判斷方法

1)原始碼

public boolean isPresent() {
    return value != null;
}
public void ifPresent(Consumer<? super T> consumer) {
    if (value != null)
        consumer.accept(value);
}
public T orElse(T other) {
    return value != null ? value : other;
 }   
public T orElseGet(Supplier<? extends T> other) {
     return value != null ? value : other.get();
}
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
   if (value != null) {
      return value;
   } else {
      throw exceptionSupplier.get();
   }
}

2)方法說明

  • isPresent(): 判斷optional是否為空,如果空則返回false,否則返回true
  • ifPresent(Consumer c): 如果optional不為空,則將optional中的物件傳給Comsumer函式
  • orElse(T other): 如果optional不為空,則返回optional中的物件;如果為null,則返回 other 這個物件。
  • orElseGet(Supplier other): 如果optional不為空,則返回optional中的物件;如果為null,否則呼叫其他函式並返回撥用的結果
  • orElseThrow(Supplier exception): 如果optional不為空,則返回optional中的物件;如果為null,則丟擲Supplier函式生成的異常

3)測試程式碼

public static void main(String[] args) {
    Integer value1 = null;
    Integer value2 = 1;
    Optional<Integer> optional1 = Optional.ofNullable(value1);
    Optional<Integer> optional2 = Optional.of(value2);
    try {
        if(optional1.isPresent()){
            System.out.println("optional1的isPresent結果不為空");
        }else{
            System.out.println("optional1的isPresent結果為空");
        }
    }catch (Exception e){
        System.out.println("optional1的isPresent判空失敗,原因:"+e.getMessage());
    }
    try {
        if(optional2.isPresent()){
            System.out.println("optional2的isPresent結果不為空");
        }else{
            System.out.println("optional2的isPresent結果為空");
        }
    }catch (Exception e){
        System.out.println("optional2的isPresent判空失敗,原因:"+e.getMessage());
    }


    optional1.ifPresent(t->{
        int i =t+1;
        System.out.println("optional1處理後的值是"+i);
    });
    optional2.ifPresent(t->{
        int i =t+1;
        System.out.println("optional2處理後的值是"+i);});


    Integer value3 = 2;
    Integer result = optional1.orElse(value3);
    System.out.println("optional1執行orElse處理後的值是"+result);


    result = optional2.orElse(value3);
    System.out.println("optional2執行orElse處理後的值是"+result);


    result = optional1.orElseGet(()-> new Integer(-1));
    System.out.println("optional1執行orElseGet處理後的值是"+result);


    result = optional2.orElseGet(()-> new Integer(-1));
    System.out.println("optional2執行orElseGet處理後的值是"+result);
    try {
    result = optional1.orElseThrow (()-> new RuntimeException("值是空的"));
    System.out.println("optional1執行orElseThrow處理後的值是"+result);
    }catch (Exception e){
        System.out.println("optional1的orElseThrow丟擲異常:"+e.getMessage());
    }
    try {
    result = optional2.orElseThrow (()-> new RuntimeException("值是空的"));
    System.out.println("optional2執行orElseThrow處理後的值是"+result);
    }catch (Exception e){
        System.out.println("optional2的orElseThrow丟擲異常:"+e.getMessage());

4)執行結果

Optional原始碼解析與實踐_java_03

2.3.5 過濾方法

1)原始碼

public Optional<T> filter(Predicate<? super T> predicate) {
    Objects.requireNonNull(predicate);
    if (!isPresent())
        return this;
    else
        return predicate.test(value) ? this : empty();
}

2)方法說明

filter(Predicate p): 如果optional不為空,則執行Predicate p,如果p的結果為true,則返回原本的optional,否則返回空的optional

3)測試程式碼

public static void main(String[] args) {
    Integer value1 = 5;
    Integer value2 = 6;
    Optional<Integer> optional1 = Optional.ofNullable(value1);
    Optional<Integer> optional2 = Optional.of(value2);


    Optional<Integer> result =optional1.filter(t->t > 5);
    System.out.println("optional1的filter後的值:"+result);
     result =optional2.filter(t->t > 5);
    System.out.println("optional2的filter後的值:"+result);

4)執行結果

Optional原始碼解析與實踐_Optional_04


2.3.6 對映方法

1)原始碼

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));
    }
}
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent())
        return empty();
    else {
        return Objects.requireNonNull(mapper.apply(value));
    }
}

2)方法說明

  • map(Function mapper): 如果存在一個值,則對其應用提供的對映函式,如果結果非空,則返回描述結果的Optional。 否則返回一個空的Optional。
  • flatMap(Function< T,Optional> mapper): 如果有值,則對其應用提供的可選對映函式,返回結果,否則返回空的可選函式。 這個方法類似於map(Function),但是提供的對映器的結果已經是一個可選的,如果呼叫,flatMap不會用額外的可選的包裝它。
  • 區別:map會自動將u放到optional中,而flatMap則需要手動給u建立一個optional

3)測試程式碼

    public static void main(String[] args) {
        User user1 = null;
        User user2 = new User("user2名字",19);
        Optional<User> optional1 = Optional.ofNullable(user1);
        Optional<User> optional2 = Optional.of(user2);
        System.out.println("=========map==========");
        System.out.println("optional1的map前的值:"+optional1);
        Optional<String> result =optional1.map(t->t.getName());
        System.out.println("optional1的map後的值:"+result);


        System.out.println("optional2的map前的值:"+optional2);
        result =optional2.map(t->t.getName());
        System.out.println("optional2的map後的值:"+result);


        System.out.println("===========flatMap========");


        System.out.println("optional1的flatMap前的值:"+optional1);
        Optional<Integer> result2 =optional1.flatMap(t->Optional.ofNullable(t.getAge()));
        System.out.println("optional1的flatMap後的值:"+result2);


        System.out.println("optional2的flatMap前的值:"+optional2);
        result2 =optional2.flatMap(t->Optional.ofNullable(t.getAge()));
        System.out.println("optional2的flatMap後的值:"+result2);

    }
public class User {
    String name;
    Integer age;
    public User(String name,Integer age){
        this.name = name;
        this.age=age;
    }


    public String getName() {
        return name;
    }


    public Integer getAge() {
        return age;

4)執行結果

Optional原始碼解析與實踐_Optional_05


3 應用例項

3.1 錯誤用法

  • 由於Optional並沒有實現Serializable介面,所以不能作為類的屬性。
  • 不要把Optional作為方法的引數。
  • 把if(x!=null)直接換成Optional.ofNullable(x).isPresent(),這樣有過度編碼的嫌疑。
  • 直接使用Optional.get()的返回值進行操作,String result =Optional.ofNullable(null).get().toString();這樣還是會丟擲異常的。

3.2 建議用法

A類有屬性B類,B類有屬性C類,C類有name這個欄位。
使用Optional之前:

if(atest!=null){
    Btest btest =atest.getBtest();
    if(btest!=null){
      Ctest ctest = btest.getCtest();
      if (ctest != null) {
          name =ctest.getName();
      }
    }
}

使用Optional之後:

name = Optional.ofNullable(atest).map(t->t.getBtest()).map(t->t.getCtest()).map(t->t.getName()).orElse("預設值");

程式碼是不是看上去更整潔了呢?

4 總結

透過對Optional原始碼解析和用例測試程式碼的執行結果,可以看出使用Optional可以最佳化null值判斷程式碼,讓程式碼變得更加優雅和整潔。


作者:陳昌浩


相關文章