天天看點

告别 NPE,全網最全 Optional 實戰了解5. Optional使用注意事項6. jdk1.9對Optional優化

1. 前言

相信不少小夥伴已經被java的NPE(Null Pointer Exception)所謂的空指針異常搞的頭昏腦漲, 有大佬說過“防止 NPE,是程式員的基本修養。”但是修養歸修養,也是我們程式員最頭疼的問題之一。之前,Google Guava項目曾提出用Optional類來包裝對象進而解決NullPointerException。受此影響,JDK8的類中也引入了Optional類,在新版的SpringData Jpa和Spring Redis Data中都已實作了對該方法的支援。我們今天就要盡可能的利用Java8的新特性 Optional來盡量簡化代碼同時高效處理NPE(Null Pointer Exception 空指針異常)

2. 初識Optional

首先我們看下面一段代碼,肯定會很熟悉

public static String getFirstName(User user)
    {
        if(null == user)
        {
            return "Unkown";
        }
        return student.getFirstName();
        
    }
           

從上面看出,就是對這個執行個體是不是空做了判斷,然後我們看下使用java8 的Optional類之後的代碼

public static String getFirstName(User user)
    {
       return Optional.ofNullable(user).map(u -> u.getFirstName()).orElse("Unkown");
        
    }
           

簡單來說,Opitonal類就是Java提供的為了解決大家平時判斷對象是否為空用 會用 null!=obj 這樣的方式存在的判斷,進而令人頭疼導緻NPE(Null Pointer Exception 空指針異常),同時Optional的存在可以讓代碼更加簡單,可讀性跟高,代碼寫起來更高效.

下面,我們就高效的學習一下神奇的Optional類!

3. Optional對象建立

首先我們先打開Optional的内部,去一探究竟 先把幾個建立Optional對象的方法提取出來

public final class Optional<T> {
   private static final Optional<?> EMPTY = new Optional<>();
   private final T value;
   //我們可以看到兩個構造方格都是private 私有的
   //說明 我們沒辦法在外面去new出來Optional對象
   private Optional() {
        this.value = null;
    }
   private Optional(T value) {
        this.value = Objects.requireNonNull(value);
    }
    //這個靜态方法大緻 是建立出一個包裝值為空的一個對象因為沒有任何參數指派
   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);
    }
    //這個靜态方法大緻是 如果參數value為空,則建立空對象,如果不為空,則建立有參對象
   public static <T> Optional<T> ofNullable(T value) {
        return value == null ? empty() : of(value);
    }
 }
複制代碼
           

再做一個簡單的執行個體展示 與上面對應

        // 1、建立一個包裝對象值為空的Optional對象
        Optional<String> optEmpty = Optional.empty();
        // 2、建立包裝對象值非空的Optional對象
        Optional<String> optOf = Optional.of("optional");
        // 3、建立包裝對象值允許為空也可以不為空的Optional對象
        Optional<String> optOfNullable1 = Optional.ofNullable(null);
        Optional<String> optOfNullable2 = Optional.ofNullable("optional");
複制代碼
           

我們關于建立Optional對象的内部方法大緻分析完畢 接下來也正式的進入Optional的學習與使用中

4. Optional類的使用

    序号   方法 方法說明
1
private Optional()      
 無參構造,構造一個空Optional
2
private Optional(T value)      
 根據傳入的非空value建構Optional
3
public static<T> Optional<T> empty()      
傳回一個空的Optional,該執行個體的value為空
4
public static <T> Optional<T> of(T value)      
根據傳入的非空value建構Optional,與Optional(T value)方法作用相同
5
public static <T> Optional<T> ofNullable(T value)      

 與of(T value)方法不同的是,ofNullable(T value)允許你傳入一個空的value,

當傳入的是空值時其建立一個空Optional,當傳入的value非空時,與of()作用相同

6
public T get()      
 傳回Optional的值,如果容器為空,則抛出NoSuchElementException異常
7
public boolean isPresent()      
 判斷當家Optional是否已設定了值
8
public void ifPresent(Consumer<? super T> consumer)      
 判斷當家Optional是否已設定了值,如果有值,則調用Consumer函數式接口進行處理
9
public Optional<T> filter(Predicate<? super T> predicate)      
 如果設定了值,且滿足Predicate的判斷條件,則傳回該Optional,否則傳回一個空的Optional
10
public<U> Optional<U> map(Function<? super T, ? extends U> mapper)      
 如果Optional設定了value,則調用Function對值進行處理,并傳回包含處理後值的Optional,否則傳回空Optional
11
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper)      
 與map()方法類型,不同的是它的mapper結果已經是一個Optional,不需要再對結果進行包裝
12
public T orElse(T other)      
 如果Optional值不為空,則傳回該值,否則傳回other 
13
public T orElseGet(Supplier<? extends T> other)      
如果Optional值不為空,則傳回該值,否則根據other另外生成一個
14
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)
throws X      
如果Optional值不為空,則傳回該值,否則通過supplier抛出一個異常

4.1 Optional.get()方法(傳回對象的值)

get()方法是傳回一個option的執行個體值 ,如果value不為空則做傳回,如果為空則抛出異常 "No value present" ,源碼:

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

執行個體:

        User user=new User();
        user.setFirstName("Tom");
        Optional.ofNullable(user).get();
           

4.2 Optional.isPresent()方法(判讀是否為空)

isPresent()方法就是會傳回一個boolean類型值,如果對象不為空則為真,如果為空則false ,源碼:

 public boolean isPresent() {
        return value != null;
    }
           

執行個體:

       
        User user=new User();
        person.setFirstName("Tom");
        if (Optional.ofNullable(user).isPresent()){
        //寫不為空的邏輯
        System.out.println("不為空");
        }else{
         //寫為空的邏輯
         System.out.println("為空");
        }
           

4 .3 Optional.ifPresent()方法(判讀是否為空并傳回函數)

如果對象非空,則運作函數體 ,源碼:

  public void ifPresent(Consumer<? super T> consumer) {
        //如果value不為空,則運作accept方法體
        if (value != null)
            consumer.accept(value);
    }
           

執行個體:

        User user = new User();
        user.setFirstName("Tom");
        Optional.ofNullable(user).ifPresent(u -> System.out.println("姓名"+u.getFirstName()));

           

4.4 Optional.filter()方法(過濾對象)

filter()方法大緻意思是,接受一個對象,然後對他進行條件過濾,如果條件符合則傳回Optional對象本身,如果不符合則傳回空Optional ,源碼:

    public Optional<T> filter(Predicate<? super T> predicate) {
        Objects.requireNonNull(predicate);
        //如果為空直接傳回this
        if (!isPresent())
            return this;
        else
        //判斷傳回本身還是空Optional
            return predicate.test(value) ? this : empty();
    }
           

執行個體:

        User user = new User();
        user.setFirstName("Tom");
        Optional.ofNullable(user).filter(u -> u.getFirstName().equeal("Toms"));
           

4.5 Optional.map()方法(對象進行二次包裝)

map()方法将對應Funcation函數式接口中的對象,進行二次運算,封裝成新的對象然後傳回在Optional中 ,源碼:

 public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
        Objects.requireNonNull(mapper);
        //如果為空傳回自己
        if (!isPresent())
            return empty();
        else {
        //否則傳回用方法修飾過的Optional
            return Optional.ofNullable(mapper.apply(value));
        }
    }
           

執行個體:

        User user = new User();
        user.setFirstName("Tom");
        String optName = Optional.ofNullable(user).map(u -> u.getFirstName()).orElse("firstName為空");
           

4.6 Optional.flatMap()方法(Optional對象進行二次包裝)

map()方法将對應Optional< Funcation >函數式接口中的對象,進行二次運算,封裝成新的對象然後傳回在Optional中, 源碼:

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

執行個體:

        User user = new User();
        user.setFirstName("Tom");
        Optional<Object> optName = Optional.ofNullable(user).map(u -> Optional.ofNullable(u.getFirstName()).orElse("firstName為空"));

           

4.7 Optional.orElse()方法(為空傳回對象)

如果包裝對象為空的話,就執行orElse方法裡的value,如果非空,則傳回寫入對象 ,源碼:

    public T orElse(T other) {
    //如果非空,傳回value,如果為空,傳回other
        return value != null ? value : other;
    }
           

執行個體:

        User user = new User();
        user.setFirstName("Tom");
        Optional.ofNullable(user).orElse(new User("Tom"));

           

4.8 Optional.orElseGet()方法(為空傳回Supplier對象)

這個與orElse很相似,入參不一樣,入參為Supplier對象,為空傳回傳入對象的.get()方法,如果非空則傳回目前對象 ,源碼:

    public T orElseGet(Supplier<? extends T> other) {
        return value != null ? value : other.get();
    }

           

執行個體:

         Optional<Supplier<User>> sup=Optional.ofNullable(User::new);
        //調用get()方法,此時才會調用對象的構造方法,即獲得到真正對象
         Optional.ofNullable(user).orElseGet(sup.get());

           

Suppiler是一個接口,是類似Spring的懶加載,聲明之後并不會占用記憶體,隻有執行了get()方法之後,才會調用構造方法建立出對象 建立對象的文法的話就是

Supplier<User> supUser= User::new;

 需要使用時

sup

User

.get()

即可

4.9 Optional.orElseThrow()方法(為空傳回異常)

如果為空,就抛出定義的異常,如果不為空傳回目前對象, 源碼:

    public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
        if (value != null) {
            return value;
        } else {
            throw exceptionSupplier.get();
        }
    }
           

執行個體:這個就貼實戰源碼了

        Member member = memberService.selectByPhone(request.getPhone());
        Optional.ofNullable(member).orElseThrow(() -> new ServiceException("沒有查詢的相關資料"));
           

4.10 相似方法進行對比分析

可能小夥伴看到這,沒用用過的話會覺得orElse()和orElseGet()還有orElseThrow()很相似,map()和flatMap()好相似 哈哈哈不用着急,都是從這一步過來的,我再給大家總結一下不同方法的異同點 orElse()和orElseGet()和orElseThrow()的異同點

方法效果類似,如果對象不為空,則傳回對象,如果為空,則傳回方法體中的對應參數,是以可以看出這三個方法體中參數是不一樣的 orElse(T 對象) orElseGet(Supplier < T >對象) orElseThrow(異常)

map()和orElseGet的異同點

方法效果類似,對方法參數進行二次包裝,并傳回,入參不同 map(function函數) flatmap(Optional< function >函數)

具體要怎麼用,要根據業務場景以及代碼規範來定義,下面可以簡單看一下我在實戰中怎用使用神奇的Optional

5. Optional使用注意事項

Optional真麼好用,真的可以完全替代if判斷嗎? 我想這肯定是大家使用完之後Optional之後可能會産生的想法,答案是否定的 舉一個最簡單的栗子:例子1:如果我隻想判斷對象的某一個變量是否為空并且做出判斷呢?

User user = new User();
user.setFirstName("");
//普通判斷
if(StringUtils.isNotBlank(user.getFirstName())){
  //名稱不為空執行代碼塊
}
//使用Optional做判斷
Optional.ofNullable(user).map(u -> u.getFirstName()).orElse("firstName為空");
           

我覺得這個例子就能很好的說明這個問題,隻是一個很簡單判斷,如果用了Optional我們還需要考慮包裝值,考慮代碼書寫,考慮方法調用,雖然隻有一行,但是可讀性并不好,如果别的程式員去讀,我覺得肯定沒有if看的明顯

6. jdk1.9對Optional優化

首先增加了三個方法: or()、ifPresentOrElse() 和 stream()。 

6.1 ifPresentOrElse(Consumer,Runnable)

ifPresentOrElse() 方法有兩個參數:一個 Consumer 和一個 Runnable。如果對象不為空,會執行 Consumer 的動作,否則運作 Runnable。相比ifPresent()多了OrElse判斷。

ifPresentOrElse(Consumer,Runnable)的語義可以解釋為:ifPresent就Consumer,OrElse就Runnable。這是Java 9 才有的增強方法。
           

執行個體:Stream流過濾3的倍數,然後通過findFirst找到第一個3的倍數。如果找到一個這樣的值,就print控制台列印出來;如果沒找到一個這樣的值,就輸出"沒有找到3的倍數"

IntStream.of(1, 2, 4)
          .filter(i -> i % 3 == 0)
          .findFirst()
          .ifPresentOrElse(System.out::println, () -> {  
              System.out.println("沒有找到3的倍數");
          });
           

6.2 Optional.or(Supplier)

or() 與orElse等方法相似,如果對象不為空傳回對象,如果為空則傳回or()方法中預設的值。

執行個體:過濾數組['a', 'b', 'c'],isDigit判斷數組中是否有數字字元,明顯沒有,是以findFirst找不到一個這樣的值。是以生成一個預設值: 

Optional.of('0')

char digit = Stream.of('a', 'b', 'c')
                    .filter(e -> Character.isDigit(e))
                    .findFirst()
                    .or(() -> Optional.of('0')).get();
 System.out.println(digit);   //0
           

6.3 Optional.stream()

**stream()**将Optional轉換成stream,如果有值就傳回包含值的stream,如果沒值,就傳回空的stream。

執行個體:本例中

Optional.stream()

方法傳回僅包含一個最大值元素的Stream流。

OptionalInt opt1 = IntStream.of(2, 5, 6).max();  //求最大值
 OptionalInt opt2 = IntStream.of(1, 3, 7).max();  //求最大值
 IntStream.concat(opt1.stream(), opt2.stream())  //将2個流合并
          .forEach(System.out::println);   //将合并後的流資料列印 6、7