Optional 類主要解決的問題是臭名昭著的空指針異常(NullPointerException)。是一個包含有可選值的包裝類,這意味着 Optional 類既可以含有對象也可以為空。
在這段代碼就可能產生空異常;
String isocode = user.getAddress().getCountry().getIsocode().toUpperCase(); //需要檢查: if (user != null) { Address address = user.getAddress(); if (address != null) { Country country = address.getCountry(); if (country != null) { String isocode = country.getIsocode(); if (isocode != null) { isocode = isocode.toUpperCase(); } } } }
Optional類的依賴依然還是函數接口那一套東西:
import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier;
是要把面向接口編程走到底了。私有字段只有一個:
private final T 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); } public static <T> Optional<T> ofNullable(T value) { return value == null ? empty() : of(value); } 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)); } } ......
創建 Optional 包裝實例
建空
Optional<Insurance> emptyOpt = Optional.empty(); try { Insurance obj = emptyOpt.get(); System.out.println("不為空"); } catch (NoSuchElementException ex) { System.out.println("為空"); }
嘗試訪問 emptyOpt 變量的值會導致 NoSuchElementException,因為原聲代碼:
public T get() { if (value == null) { throw new NoSuchElementException("No value present"); } return value; }
建可能為空(ofNullable不拋異常)
Optional<Insurance> opt = Optional.ofNullable(null);
不會異常
如此神奇,原因在於原生代碼已經進行了判空,把私有value定成了empty(),這種不拋異常也會產生一個問題,那就是在消費實例的時候,還是要過濾掉空值。
public static <T> Optional<T> ofNullable(T value) { return value == null ? empty() : of(value); }
建為空時拋異常(即of不可為空)
public static void whenCreateOfEmptyOptional_thenNullPointerException() { Insurance ins = new Insurance(); Optional<Insurance> opt = Optional.of(ins); ins=null; try { opt = Optional.of(ins); System.out.println("不為空"); } catch (NullPointerException ex) { System.out.println("為空"); } }
會進入異常。跟蹤原生代碼發現了原因:
public static <T> T requireNonNull(T obj) { if (obj == null) throw new NullPointerException(); return obj; }
訪問 Optional 對象的值
get
從 Optional 實例中取回實際值對象的方法之一是使用 get() 方法:
@Test public void whenCreateOfNullableOptional_thenOk() { String name = "John"; Optional<String> opt = Optional.ofNullable(name); assertEquals("John", opt.get()); }
這個方法會在值為 null 的時候拋出異常。要避免異常,你可以選擇首先驗證是否有值:
@Test public void whenCheckIfPresent_thenOk() { User user = new User("john@gmail.com", "1234"); Optional<User> opt = Optional.ofNullable(user); assertTrue(opt.isPresent()); assertEquals(user.getEmail(), opt.get().getEmail()); }
ifPresent的使用
原生代碼:
public void ifPresent(Consumer<? super T> consumer) { if (value != null) consumer.accept(value); }
只有當值不為空,才執行消費者方法accept,這是比較安全的。
public static void printName(Insurance obj) { Optional.ofNullable(obj).ifPresent(u -> System.out.println("The name is : " + u.getName())); } Insurance obj = new Insurance(); obj.setName("張三"); Insurance objNull = null; printName(obj); printName(objNull); //輸出:The name is : 張三
isPresent的使用
感覺有了ifPresent,不需要再用isPresent來判斷空了,否則感覺非常啰嗦。
orElse的使用
在對象為空的時候返回默認值。它的工作方式非常直接,如果有值則返回該值,否則返回傳遞給它的參數值。
Insurance obj = new Insurance(); obj.setName("張三"); Insurance objNull = null; Insurance result = Optional.ofNullable(objNull).orElse(obj); System.out.println(obj.hashCode()); System.out.println(result.hashCode());
result實際是返回的obj,而不是null。
filter的使用
filter()方法接受參數為Predicate對象,用於對Optional對象進行過濾,如果符合Predicate的條件,返回Optional對象本身,否則返回一個空的Optional對象
public static void filterAge(Student student) { Optional.ofNullable(student).filter( u -> u.getAge() > 18).ifPresent(u -> System.out.println("The student age is more than 18.")); }
map的使用
map()方法的參數為Function(函數式接口)對象,map()方法將Optional中的包裝對象用Function函數進行運算,並包裝成新的Optional對象(包裝對象的類型可能改變)
原生代碼:
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)); } }
下面代碼中,先用ofNullable()方法構造一個Optional<Student>對象,然后用map()計算學生的年齡,返回Optional<Integer>對象(如果student為null, 返回map()方法返回一個空的Optinal對象)
public static Optional<Integer> getAge(Student student) { return Optional.ofNullable(student).map(u -> u.getAge()); }
flatMap的使用
跟map()方法不同的是,入參Function函數的返回值類型為Optional<U>類型,而不是U類型,這樣flatMap()能將一個二維的Optional對象映射成一個一維的對象,
總而言之,map進去是什么,出來維度不變,flatMap是為了拍扁結果。
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)); } } //例子 Optional<String> nonEmptyGender = Optional.of("male"); Optional<String> emptyGender = Optional.empty(); System.out.println("Non-Empty Optional:: " + nonEmptyGender.map(String::toUpperCase)); System.out.println("Empty Optional :: " + emptyGender.map(String::toUpperCase)); Optional<Optional<String>> nonEmptyOtionalGender = Optional.of(Optional.of("male")); System.out.println("Optional value :: " + nonEmptyOtionalGender); System.out.println("Optional.map :: " + nonEmptyOtionalGender.map(gender -> gender.map(String::toUpperCase))); System.out.println("Optional.flatMap :: " + nonEmptyOtionalGender.flatMap(gender -> gender.map(String::toUpperCase)));
封裝可能的空值
使用ofNullable,比如在一些map中的key為空的情況下使用:
Optional<Insurance> opt = Optional.ofNullable(map.get("someNullKey"));