【Java8新特性】不瞭解Optional類,簡歷上別說你懂Java8!!

冰河團隊發表於2020-05-30

寫在前面

最近,很多讀者出去面試都在Java8上栽了跟斗,事後自己分析,確實對Java8的新特性一知半解。然而,卻在簡歷顯眼的技能部分寫著:熟練掌握Java8的各種新特性,能夠迅速使用Java8開發高併發應用!這不,又一名讀者因為寫了熟練掌握Java8的新特性而被面試官虐的體無完膚!我不是說不能寫,可以這樣寫!但是,我們在寫熟練掌握Java8新特性的時候,應該靜下心來好好想想自己是否真的掌握了Java8。如果自己心中對是否掌握了Java8這個問題模稜兩可的話,那確實要好好靜下心來為自己充電了!一定要從模稜兩可到徹底掌握Java8,那到時就不是面試官虐你了,而是你吊打面試官!!

什麼是Optional類?

Optional 類(java.util.Optional) 是一個容器類,代表一個值存在或不存在,原來用 null 表示一個值不存在,現在 Optional 可以更好的表達這個概念。並且可以避免空指標異常。

Optional類常用方法:

  • Optional.of(T t) : 建立一個 Optional 例項。
  • Optional.empty() : 建立一個空的 Optional 例項。
  • Optional.ofNullable(T t):若 t 不為 null,建立 Optional 例項,否則建立空例項。
  • isPresent() : 判斷是否包含值。
  • orElse(T t) : 如果呼叫物件包含值,返回該值,否則返回t。
  • orElseGet(Supplier s) :如果呼叫物件包含值,返回該值,否則返回 s 獲取的值。
  • map(Function f): 如果有值對其處理,並返回處理後的Optional,否則返回 Optional.empty()。
  • flatMap(Function mapper):與 map 類似,要求返回值必須是Optional。

Optional類示例

1.建立Optional類

(1)使用empty()方法建立一個空的Optional物件:

Optional<String> empty = Optional.empty();

(2)使用of()方法建立Optional物件:

String name = "binghe";
Optional<String> opt = Optional.of(name);
assertEquals("Optional[binghe]", opt.toString());

傳遞給of()的值不可以為空,否則會丟擲空指標異常。例如,下面的程式會丟擲空指標異常。

String name = null;
Optional<String> opt = Optional.of(name);

如果我們需要傳遞一些空值,那我們可以使用下面的示例所示。

String name = null;
Optional<String> opt = Optional.ofNullable(name);

使用ofNullable()方法,則當傳遞進去一個空值時,不會丟擲異常,而只是返回一個空的Optional物件,如同我們用Optional.empty()方法一樣。

2.isPresent

我們可以使用這個isPresent()方法檢查一個Optional物件中是否有值,只有值非空才返回true。

Optional<String> opt = Optional.of("binghe");
assertTrue(opt.isPresent());

opt = Optional.ofNullable(null);
assertFalse(opt.isPresent());

在Java8之前,我們一般使用如下方式來檢查空值。

if(name != null){
    System.out.println(name.length);
}

在Java8中,我們就可以使用如下方式來檢查空值了。

Optional<String> opt = Optional.of("binghe");
opt.ifPresent(name -> System.out.println(name.length()));

3.orElse和orElseGet

(1)orElse

orElse()方法用來返回Optional物件中的預設值,它被傳入一個“預設引數‘。如果物件中存在一個值,則返回它,否則返回傳入的“預設引數”。

String nullName = null;
String name = Optional.ofNullable(nullName).orElse("binghe");
assertEquals("binghe", name);

(2)orElseGet

與orElse()方法類似,但是這個函式不接收一個“預設引數”,而是一個函式介面。

String nullName = null;
String name = Optional.ofNullable(nullName).orElseGet(() -> "binghe");
assertEquals("binghe", name);

(3)二者有什麼區別?

要想理解二者的區別,首先讓我們建立一個無參且返回定值的方法。

public String getDefaultName() {
    System.out.println("Getting Default Name");
    return "binghe";
}

接下來,進行兩個測試看看兩個方法到底有什麼區別。

String text;
System.out.println("Using orElseGet:");
String defaultText = Optional.ofNullable(text).orElseGet(this::getDefaultName);
assertEquals("binghe", defaultText);

System.out.println("Using orElse:");
defaultText = Optional.ofNullable(text).orElse(getDefaultName());
assertEquals("binghe", defaultText);

在這裡示例中,我們的Optional物件中包含的都是一個空值,讓我們看看程式執行結果:

Using orElseGet:
Getting default name...
Using orElse:
Getting default name...

兩個Optional物件中都不存在value,因此執行結果相同。

那麼,當Optional物件中存在資料會發生什麼呢?我們一起來驗證下。

String name = "binghe001";

System.out.println("Using orElseGet:");
String defaultName = Optional.ofNullable(name).orElseGet(this::getDefaultName);
assertEquals("binghe001", defaultName);

System.out.println("Using orElse:");
defaultName = Optional.ofNullable(name).orElse(getDefaultName());
assertEquals("binghe001", defaultName);

執行結果如下所示。

Using orElseGet:
Using orElse:
Getting default name...

可以看到,當使用orElseGet()方法時,getDefaultName()方法並不執行,因為Optional中含有值,而使用orElse時則照常執行。所以可以看到,當值存在時,orElse相比於orElseGet,多建立了一個物件。如果建立物件時,存在網路互動,那系統資源的開銷就比較大了,這是需要我們注意的一個地方。

4.orElseThrow

orElseThrow()方法當遇到一個不存在的值的時候,並不返回一個預設值,而是丟擲異常。

String nullName = null;
String name = Optional.ofNullable(nullName).orElseThrow( IllegalArgumentException::new);

5.get

get()方法表示是Optional物件中獲取值。

Optional<String> opt = Optional.of("binghe");
String name = opt.get();
assertEquals("binghe", name);

使用get()方法也可以返回被包裹著的值。但是值必須存在。當值不存在時,會丟擲一個NoSuchElementException異常。

Optional<String> opt = Optional.ofNullable(null);
String name = opt.get();

6.filter

接收一個函式式介面,當符合介面時,則返回一個Optional物件,否則返回一個空的Optional物件。

String name = "binghe";
Optional<String> nameOptional = Optional.of(name);
boolean isBinghe = nameOptional.filter(n -> "binghe".equals(name)).isPresent();
assertTrue(isBinghe);
boolean isBinghe001 = nameOptional.filter(n -> "binghe001".equals(name)).isPresent();
assertFalse(isBinghe001);

使用filter()方法會過濾掉我們不需要的元素。

接下來,我們再來看一例示例,例如目前有一個Person類,如下所示。

public class Person{
    private int age;
    public Person(int age){
        this.age = age;
    }
    //省略get set方法
}

例如,我們需要過濾出年齡在25歲到35歲之前的人群,那在Java8之前我們需要建立一個如下的方法來檢測每個人的年齡範圍是否在25歲到35歲之前。

public boolean filterPerson(Peron person){
    boolean isInRange = false;
    if(person != null && person.getAge() >= 25 && person.getAge() <= 35){
        isInRange =  true;
    }
    return isInRange;
}

看上去就挺麻煩的,我們可以使用如下的方式進行測試。

assertTrue(filterPerson(new Peron(18)));
assertFalse(filterPerson(new Peron(29)));
assertFalse(filterPerson(new Peron(16)));
assertFalse(filterPerson(new Peron(34)));
assertFalse(filterPerson(null));

如果使用Optional,效果如何呢?

public boolean filterPersonByOptional(Peron person){
     return Optional.ofNullable(person)
       .map(Peron::getAge)
       .filter(p -> p >= 25)
       .filter(p -> p <= 35)
       .isPresent();
}

使用Optional看上去就清爽多了,這裡,map()僅僅是將一個值轉換為另一個值,並且這個操作並不會改變原來的值。

7.map

如果有值對其處理,並返回處理後的Optional,否則返回 Optional.empty()。

List<String> names = Arrays.asList("binghe001", "binghe002", "", "binghe003", "", "binghe004");
Optional<List<String>> listOptional = Optional.of(names);

int size = listOptional
    .map(List::size)
    .orElse(0);
assertEquals(6, size);

在這個例子中,我們使用一個List集合封裝了一些字串,然後再把這個List使用Optional封裝起來,對其map(),獲取List集合的長度。map()返回的結果也被封裝在一個Optional物件中,這裡當值不存在的時候,我們會預設返回0。如下我們獲取一個字串的長度。

String name = "binghe";
Optional<String> nameOptional = Optional.of(name);

int len = nameOptional
    .map(String::length())
    .orElse(0);
assertEquals(6, len);

我們也可以將map()方法與filter()方法結合使用,如下所示。

String password = " password ";
Optional<String> passOpt = Optional.of(password);
boolean correctPassword = passOpt.filter(
    pass -> pass.equals("password")).isPresent();
assertFalse(correctPassword);

correctPassword = passOpt
    .map(String::trim)
    .filter(pass -> pass.equals("password"))
    .isPresent();
assertTrue(correctPassword);

上述程式碼的含義就是對密碼進行驗證,檢視密碼是否為指定的值。

8.flatMap

與 map 類似,要求返回值必須是Optional。

假設我們現在有一個Person類。

public class Person {
    private String name;
    private int age;
    private String password;
 
    public Optional<String> getName() {
        return Optional.ofNullable(name);
    }
 
    public Optional<Integer> getAge() {
        return Optional.ofNullable(age);
    }
 
    public Optional<String> getPassword() {
        return Optional.ofNullable(password);
    }
    // 忽略get set方法
}

接下來,我們可以將Person封裝到Optional中,並進行測試,如下所示。

Person person = new Person("binghe", 18);
Optional<Person> personOptional = Optional.of(person);

Optional<Optional<String>> nameOptionalWrapper = personOptional.map(Person::getName);
Optional<String> nameOptional = nameOptionalWrapper.orElseThrow(IllegalArgumentException::new);
String name1 = nameOptional.orElse("");
assertEquals("binghe", name1);

String name = personOptional
    .flatMap(Person::getName)
    .orElse("");
assertEquals("binghe", name);

注意:方法getName返回的是一個Optional物件,如果使用map,我們還需要再呼叫一次get()方法,而使用flatMap()就不需要了。

寫在最後

如果覺得文章對你有點幫助,請微信搜尋並關注「 冰河技術 」微信公眾號,跟冰河學習Java8新特性。

最後,附上Java8新特性核心知識圖,祝大家在學習Java8新特性時少走彎路。
在這裡插入圖片描述

相關文章