java8新特性→Optional:適用於層級處理非空判斷(依賴上一步操作)的場合


一、Optional入門

Optional是jdk1.8引入的類型,Optional是一個容器對象,它包括了我們需要的對象,使用isPresent方法判斷所包含對象是否為空,isPresent方法返回false則表示Optional包含對象為空,否則可以使用get()取出對象進行操作。

之前的寫法:

public Person getPerson() {
        Person person = new Person();
        if (null == person) {
            return null;
        }
        return person;
    }

現在可以寫成:

public Person getPerson() {
        Person person = new Person();
        return Optional.ofNullable(person).orElse(null);
    }

其中Person類

@Data
public class Person {
    private String name;
    private Integer age;
}

Optional的優點是:

1、提醒你非空判斷。

2、將對象非空檢測標准化。

首先我們先打開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、我們可以看到兩個構造方格都是private 私有的,說明我們沒辦法在外面去new出來Optional對象。

 2、靜態方法:empty方法創建出一個包裝值為空的對象。of方法創建出一個包裝值非空的對象,ofNullable方法創建包裝對象值可以為空也可以不為空的對象。

// 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");

常用方法:

//of():為非null的值創建一個Optional
Optional<String> optional = Optional.of("bam");
// isPresent(): 如果值存在返回true,否則返回false
optional.isPresent();           // true
//get():如果Optional有值則將其返回,否則拋出NoSuchElementException
optional.get();                 // "bam"
//orElse():如果有值則將其返回,否則返回指定的其它值
optional.orElse("fallback");    // "bam"
//ifPresent():如果Optional實例有值則為其調用consumer,否則不做處理
optional.ifPresent((s) -> System.out.println(s.charAt(0)));     // "b"

使用案例1

//修改 
@Test 
public void testUpdate() { 
    Optional<CmsPage> optional = cmsPageRepository.findOne("5b17a34211fe5e2ee8c116c9"); 
    if(optional.isPresent()){ 
        CmsPage cmsPage = optional.get(); 
        cmsPage.setPageName("測試頁面01"); 
        cmsPageRepository.save(cmsPage); 
    } 
}

使用案例2:判斷從JSONObject中取出的字符串是否為空

Object access_token = tokenResult.get("access_token");
String o = (String) Optional.ofNullable(access_token).orElse("");

簡寫:

String accessToken = (String) Optional.ofNullable(tokenResult.get("access_token")).orElse("");

實際開發中,這種判斷比較常見。我們經常會對取出的值進行判斷,如果為null,則賦值為空字符串,否則為本身。

二、Optional API

Optional提供很多有用的方法,這樣我們就不用顯式進行空值檢測。Optional 類的引入很好的解決空指針異常。

Optional API地址:https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html

static <T> Optional<T>    ofNullable(T value):當value值非空時,則返回一個非空的Optional,否則返回一個空的Optional

<U> Optional<U>   map(Function<? super T,? extends U> mapper):如果value值存在,則執行提供的映射函數,如果結果不為空,返回一個非空的Optional

T  orElse(T other):如果value值存在則返回value值,如果不存在,則返回other。

<X extends Throwable> orElseThrow(Supplier<? extends X> exceptionSupplier):如果value值存在,則返回,如果不存在,則拋出一個由Supplier創建的異常。

 void  ifPresent(Consumer<? super T> consumer):如果value值存在,則調用Consumer接口(執行Lambda表達式或方法引用),否則什么都不做。

 Optional<T> filter(Predicate<? super T> predicate):如果值存在且值匹配predicate,返回一個非空的Optional,否則返回一個空的Optional。

 T  orElseGet(Supplier<? extends T> other):如果值存在,則返回該值,否則調用other並返回調用的結果

1、Optional.get()方法(返回對象的值)

get()方法是返回一個option的實例值 源碼:

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

例子:

// 1、創建一個包裝對象值為空的Optional對象
        Optional<String> optEmpty = Optional.empty();
        String s = optEmpty.get();

結果:java.util.NoSuchElementException: No value present

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

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

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

故在get之前需要調用isPresent方法進行判斷

Optional<String> optOf = Optional.of("optional");
        if (optOf.isPresent()) {
            System.out.println(optOf.get());
        }

例2:

Person person = new Person();
        person.setAge(2);
        Optional<Person> optional = Optional.ofNullable(person);
        if (optional.isPresent()) {
            System.out.println("不為空");
        }

結果:不為空

3、Optional.ifPresent()方法(判讀是否為空並返回函數)

 這個意思是如果對象非空,則運行函數體 源碼:

public void ifPresent(Consumer<? super T> consumer) {
        if (value != null)
            consumer.accept(value);
    }

上面的代碼可以簡化如下:

Person person = new Person();
        person.setAge(2);
        Optional.ofNullable(person).ifPresent(p-> System.out.println(p.getAge()));

結果:2

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

簡單實例:

Person person = new Person();
        person.setAge(2);
        Optional<Person> optional = Optional.ofNullable(person).filter(person1 -> person1.getAge() > 1);
        optional.ifPresent(p -> System.out.println(p.getAge()));

結果:2

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

例子

Person person = new Person();
        person.setAge(2);
        Integer integer = Optional.ofNullable(person).map(Person::getAge).orElse(null);
        System.out.println(integer);

結果:2

6、Optional.orElse()方法(為空返回對象)

常用方法之一,這個方法意思是如果包裝對象為空的話,就執行orElse方法里的value,如果非空,則返回寫入對象 源碼:

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

7、Optional.orElseGet()方法(為空返回Supplier對象)

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

Person person = new Person();
        person.setAge(2);
        Optional<Supplier<Person>> sup=Optional.ofNullable(Person::new);
//調用get()方法,此時才會調用對象的構造方法,即獲得到真正對象
        Person person1 = Optional.ofNullable(person).orElseGet(sup.get());
        System.out.println(person1);

結果:Person(name=null, age=2)

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

結果:Person(name=null, age=null)

8、Optional.orElseThrow()方法(為空返回異常)

這個我個人在實戰中也經常用到這個方法,方法作用的話就是如果為空,就拋出你定義的異常,如果不為空返回當前對象,在實戰中所有異常肯定是要處理好的,為了代碼的可讀性。源碼:

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

實例:這個就貼實戰源碼了

Person person = null;
        Optional.ofNullable(person).orElseThrow(() -> new RuntimeException("沒有查詢到相關數據"));

結果:

java.lang.RuntimeException: 沒有查詢到相關數據

 三、Optional API的應用

善用 Optional 可以使我們代碼中很多繁瑣、丑陋的設計變得十分優雅。

使用 Optional,我們就可以把下面這樣的代碼進行改寫。

public static String getName(User u) {
    if (u == null || u.name == null)
        return "Unknown";
    return u.name;
}

不過,千萬不要改寫成這副樣子。

public static String getName(User u) {
    Optional<User> user = Optional.ofNullable(u); // 使用Optional包裝
    if (!user.isPresent())
        return "Unknown";
    return user.get().name;
}

這樣改寫非但不簡潔,而且其操作還是和第一段代碼一樣。無非就是用 isPresent 方法來替代 u==null。這樣的改寫並不是 Optional 正確的用法,我們再來改寫一次。

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

這樣才是正確使用 Optional 的姿勢。那么按照這種思路,我們可以安心的進行鏈式調用,而不是一層層判斷了。看一段代碼:

public static String getChampionName(Competition comp) throws IllegalArgumentException {
    if (comp != null) {
        CompResult result = comp.getResult();
        if (result != null) {
            User champion = result.getChampion();
            if (champion != null) {
                return champion.getName();
            }
        }
    }
    throw new IllegalArgumentException("The value of param comp isn't available.");
}

讓我們看看經過 Optional 加持過后,這些代碼會變成什么樣子。

public static String getChampionName(Competition comp) throws IllegalArgumentException {
    return Optional.ofNullable(comp)
            .map(Competition::getResult)  // 相當於c -> c.getResult(),下同
            .map(CompResult::getChampion)
            .map(User::getName)
            .orElseThrow(()->new IllegalArgumentException("The value of param comp isn't available."));
}

還有很多不錯的使用姿勢,比如字符串為空則不打印可以這么寫:

string.ifPresent(System.out::println);

Optional 的魅力還不止於此,Optional 還有一些神奇的用法,比如 Optional 可以用來檢驗參數的合法性。

public void setName(String name) throws IllegalArgumentException {
    this.name = Optional.ofNullable(name)
                        .filter(User::isNameValid)
                        .orElseThrow(()->new IllegalArgumentException("Invalid username."));
}

 這樣寫參數合法性檢測,應該足夠優雅了吧。

 不過這還沒完,上面的兩個例子其實還不能完全反應出 Optional 的設計意圖。事實上,我們應該更進一步,減少 Optional.ofNullable 的使用。為什么呢?因為 Optional 是被設計成用來代替 null 以表示不確定性的,換句話說,只要一段代碼可能產生 null,那它就可以返回 Optional。而我們選擇用 Optional 代替 null 的原因,是 Optional 提供了一個把若干依賴前一步結果的處理結合在一起的途徑。

 Optional應用建議

Optional 就像一個處理不確定性的管道,我們在一頭丟進一個可能是 null 的東西(接口返回結果),經過層層處理,最后消除不確定性。Optional 在過程中保留了不確定性,從而把對 null 的處理移到了若干次操作的最后,以減少出現 NPE 錯誤的可能。於是,Optional 應用的建議也呼之欲出了:

  1. 適用於層級處理(依賴上一步操作)的場合

  2. 產生對象的方法若可能返回 null,可以用 Optional 包裝。

  3. 盡可能延后處理 null 的時機,在過程中使用 Optional 保留不確定性。

  4. 盡量避免使用 Optional 作為字段類型。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM