一位智者說過,沒有處理過空指針異常就不算一個真正的 Java 程序員。這固然是開玩笑,可是空指針異常確實是不少程序出錯的源頭。
因而,在 Java 8 引入了 java.util.Optional
,Optional 用來表明一種 可能有可能沒有 的數據,能夠用來緩解空指針異常的問題。javascript
簡單地說,Optional 用來避免這種代碼:html
String version = "UNKNOWN"; if(computer != null){ Soundcard soundcard = computer.getSoundcard(); if(soundcard != null){ USB usb = soundcard.getUSB(); if(usb != null){ version = usb.getVersion(); } } }
若是用 Optional 表示呢?大概是這樣:java
String version = computer.flatMap(Computer::getSoundcard) .flatMap(Soundcard::getUSB) .map(USB::getVersion) .orElse("UNKNOWN");
實際上,Optional 即函數式編程中的 Maybe。程序員
如下示例在 OptionalTest.java 中。編程
建立 Optional 有三種方式,分別是 empty、 of 和 ofNullable。oracle
empty
用來建立一個空的 Optionalide
@Test public void create_optional_with_empty() { Optional<String> empty = Optional.empty(); assertFalse(empty.isPresent()); }
of
用來建立一個非空的 Optional:函數式編程
@Test public void create_optional_with_of() { Optional<String> java = Optional.of("Java"); assertTrue(java.isPresent()); }
可是參數不能爲 null,不然會拋空指針異常:函數
@Test(expected = NullPointerException.class) public void create_optional_with_of_with_null() { Optional.of(null); }
ofNullable
用來建立一個可能爲空的 Optional:oop
@Test public void create_optional_with_ofNullable() { Optional<String> java = Optional.ofNullable("Java"); assertTrue(java.isPresent()); Optional<Object> o = Optional.ofNullable(null); assertFalse(o.isPresent()); }
可使用 isPresent
和 isEmpty
判斷 Optional 的值是否爲空。
若是 Optional 中值非 null,則返回 true,不然返回 false。
@Test public void check_optional_with_isPresent() { Optional<String> java = Optional.ofNullable("java"); Optional<Object> aNull = Optional.ofNullable(null); assertTrue(java.isPresent()); assertFalse(aNull.isPresent()); }
Java 11 開始可使用 isEmpty
。
isEmpty
與 isPresent
相反,若是爲 null 返回 true。
@Test public void check_optional_with_isEmpty() { Optional<String> java = Optional.ofNullable("java"); Optional<Object> aNull = Optional.ofNullable(null); assertFalse(java.isEmpty()); assertTrue(aNull.isEmpty()); }
關於條件的動做有 ifPresent
、orElse
、orElseGet
、orElseThrow
、or
、ifPresentOrElse
,它們執行與否取決於 Optional 的值是否爲 null。
爲了不空指針異常,咱們會常常寫下面的代碼:
if (name != null){ System.out.println(name.length); }
Optional 使用一種函數式的方式來替代上面的寫法。
ifPresent 接受一個 Consumer,在 Optional 值非 null 時調用,並接受 Optional 的值。
@Test public void condition_action_ifPresent() { Optional<String> java = Optional.ofNullable("java"); java.ifPresent((value) -> System.out.println("ifPresent accept " + value)); Optional<Object> aNull = Optional.ofNullable(null); aNull.ifPresent(value -> System.out.println("this will never execute")); }
orElse 在 Optional 值爲 null 時觸發,它接受一個參數,做爲 Optional 的默認值。
@Test public void condition_action_orElse() { assertTrue(Optional.ofNullable("java").orElse("javascript").equals("java")); assertTrue(Optional.ofNullable(null).orElse("java").equals("java")); }
orElseGet 與 orElse 相似,但 orElseGet 接受的是一個 Supplier,Supplier 返回的值做爲 Optional 的默認值。
@Test public void condition_action_orElseGet() { assertTrue(Optional.ofNullable("java").orElseGet(() -> "javascript").equals("java")); assertTrue(Optional.ofNullable(null).orElseGet(() -> "java").equals("java")); }
orElse
和 orElseGet
的函數簽名是不同的,但若是想使用一樣的函數的返回值來做爲 Optional 的默認值,咱們極可能會這麼幹:
public String getDefaultName() { System.out.println("You got a default name"); return "default"; } @Test public void difference_between_orElse_and_orElseGet() { Optional<String> java = Optional.of("java"); System.out.println("orElse:"); assertEquals("java", java.orElse(getDefaultName())); System.out.println("orElseGet:"); assertEquals("java", java.orElseGet(this::getDefaultName)); }
若 java 是 null,則 orElse 和 orElseGet 沒有什麼不一樣,getDefaultName 方法都會執行並將返回值做爲 Optional 的默認值。
當在上面的例子中,java 非 null,這時 orElse 的 getDefaultName 仍是會執行,但 orElseGet 不會。輸出:
orElse: You got a default name orElseGet:
當 getDefaultName 中有反作用或耗時操做時須要注意。
orElseThrow 與 orElse 同樣也在當 Optional 值爲 null 時觸發,但與之不一樣的是會拋出指定的異常:
@Test(expected = IllegalArgumentException.class) public void condition_action_orElseThrow() { Optional.ofNullable(null).orElseThrow(IllegalArgumentException::new); }
or 是 Java 9 中新增方法。與 orElseGet 很類似,or 也接受一個 Supplier,但 or 返回的是一個新的 Optional。
@Test public void condition_or_optional() { Optional<String> java = Optional.of("java") .or(() -> Optional.of("javascript")); Optional<Object> java1 = Optional.empty() .or(() -> Optional.of("java")); assertEquals("java", java.get()); assertEquals("java", java1.get()); }
ifPresentOrElse 是 Java 9 中新增的方法。ifPresent 就如同命令式編程中的 if-else
,它接受兩個參數,第一個爲 Consumer,在 Optional 有值時調用,第二個爲 Runnable,在無值時調用:
@Test public void condition_ifPresentOrElse() { // value is java Optional.of("java") .ifPresentOrElse(value -> System.out.println("value is " + value), () -> System.out.println("ooops")); // ooops Optional.empty() .ifPresentOrElse(value -> System.out.println("value is " + value), () -> System.out.println("ooops")); }
Optional 提供了一個 get
方法獲取值,但 get 方法只能在 Optional 有值時使用,不然會拋出 NoSuchElementException
異常:
@Test public void get_optional_with_of() { Optional<String> java = Optional.of("Java"); assertEquals("java", java.get()); } @Test(expected = NoSuchElementException.class) public void get_optional_with_of_with_null() { Optional.empty().get(); }
filter
方法用來驗證 Optional 的值是否符合條件,它接受一個 Predicate 做爲參數。若是 Optional 的值爲 null 或 Predicate 判斷不經過,則返回 empty;不然返回該 Optional。
@Test public void test_optional_by_filter() { Integer nullYear = null; Optional<Integer> integer = Optional.ofNullable(nullYear) .filter(value -> value == 2018); assertEquals(Optional.empty(), integer); Integer year = 2019; Optional<Integer> integer1 = Optional.ofNullable(year) .filter(value -> value == 2018); assertEquals(Optional.empty(), integer1); Optional<Integer> integer2 = Optional.ofNullable(year) .filter(value -> value == 2019); assertEquals("Optional[2019]", integer2.toString()); }
filter 相對傳統 if 而言省去了不少樣板代碼,如:
public boolean priceIsInRange1(Modem modem) { boolean isInRange = false; if (modem != null && modem.getPrice() != null && (modem.getPrice() >= 10 && modem.getPrice() <= 15)) { isInRange = true; } return isInRange; }
使用 Optional 實現一樣的方法:
public boolean priceIsInRange2(Modem modem2) { return Optional.ofNullable(modem2) .map(Modem::getPrice) .filter(p -> p >= 10) .filter(p -> p <= 15) .isPresent(); }
處理值的方式有 map 和 flatMap。
使用 map 能夠對 Optional 中的值進行處理並返回。
@Test public void map_optional() { Optional<String> java = Optional.of("java"); String result = java.map(String::toUpperCase).orElse(""); assertEquals("JAVA", result); }
flatMap 與 map 的區別在於 map 處理值後會包裝返回值,而 flatMap 不包裝。
public class Person { private String name; public Person(String name) { this.name = name; } public Optional<String> getName() { return Optional.ofNullable(name); } } @Test public void flatMap_optional() { Person person = new Person("john"); Optional<Person> personOptional = Optional.of(person); String byMap = personOptional.map(Person::getName) // 須要手動打開包裝 .orElse(Optional.empty()) .orElse(""); String byFlatMap = personOptional.flatMap(Person::getName) .orElse(""); assertEquals("john", byMap); assertEquals("john", byFlatMap); }
在 Java 9 中,新增了 stream 方法,能夠對 Optional 建立 stream,而後可使用 stream 上的全部方法。
若是 Optional 爲 empty,則建立一個 empty 的 stream。
@Test public void treat_optional_as_stream() { List<String> collect = Optional.of("java") .stream() .map(value -> value.concat("script")) .collect(Collectors.toList()); assertArrayEquals(new String[]{"javascript"}, collect.toArray()); // empty optional Optional<String> value = Optional.empty(); List<String> emptyStream = value.stream() .map(String::toUpperCase) .collect(Collectors.toList()); assertEquals(0, emptyStream.size()); }
因此使用 stram 也能夠篩出非 null 的 Optional 的值:
@Test public void filter_empty_by_stream() { List<Optional<String>> languages = List.of(Optional.of("java"), Optional.empty(), Optional.empty(), Optional.of("javascript")); List<String> collect = languages.stream() .flatMap(Optional::stream) .collect(Collectors.toList()); assertArrayEquals(new String[]{"java", "javascript"}, collect.toArray()); }