寫過 Java 程序的同窗,通常都遇到過 NullPointerException
:) —— 爲了避免拋出這個異常,咱們便會寫以下的代碼:java
User user = getUserById(id); if (user != null) { String username = user.getUsername(); System.out.println("Username is: " + username); // 使用 username }
可是不少時候,咱們可能會忘記寫 if (user != null)
—— 若是在開發階段就發現那還好,可是若是在開發階段沒有測試到問題,等到上線卻出了 NullPointerException
... 畫面太美,我不敢繼續想下去。數據庫
爲了解決這種尷尬的處境,JDK 終於在 Java8 的時候加入了 Optional
類,查看 Optional
的 javadoc 介紹:app
A container object which may or may not contain a non-null value. If a value is present,isPresent()
will returntrue
andget()
will return the value.
這是一個能夠包含或者不包含非 null
值的容器。若是值存在則 isPresent()
方法會返回 true
,調用 get()
方法會返回該對象。函數
JDK 提供三個靜態方法來構造一個 Optional
:
1.Optional.of(T value)
,該方法經過一個非 null
的 value 來構造一個 Optional
,返回的 Optional
包含了 value 這個值。對於該方法,傳入的參數必定不能爲 null
,不然便會拋出 NullPointerException
。測試
2.Optional.ofNullable(T value)
,該方法和 of
方法的區別在於,傳入的參數能夠爲 null
—— 可是前面 javadoc 不是說 Optional
只能包含非 null
值嗎?咱們能夠看看 ofNullable
方法的源碼:this
原來該方法會判斷傳入的參數是否爲 null
,若是爲 null
的話,返回的就是 Optional.empty()
。spa
3.Optional.empty()
,該方法用來構造一個空的 Optional
,即該 Optional
中不包含值 —— 其實底層實現仍是 若是 Optional
中的 value 爲 null
則該 Optional
爲不包含值的狀態,而後在 API 層面將 Optional
表現的不能包含 null
值,使得 Optional
只存在 包含值 和 不包含值 兩種狀態。code
前面 javadoc 也有提到,Optional
的 isPresent()
方法用來判斷是否包含值,get()
用來獲取 Optional
包含的值 —— 值得注意的是,若是值不存在,即在一個Optional.empty
上調用 get()
方法的話,將會拋出 NoSuchElementException
異常。
咱們假設 getUserById
已是個客觀存在的不能改變的方法,那麼利用 isPresent
和 get
兩個方法,咱們如今能寫出下面的代碼:對象
Optional<User> user = Optional.ofNullable(getUserById(id)); if (user.isPresent()) { String username = user.get().getUsername(); System.out.println("Username is: " + username); // 使用 username }
好像看着代碼是優美了點 —— 可是事實上這與以前判斷 null
值的代碼沒有本質的區別,反而用 Optional
去封裝 value,增長了代碼量。因此咱們來看看 Optional
還提供了哪些方法,讓咱們更好的(以正確的姿式)使用 Optional
。blog
1.ifPresent
若是 Optional
中有值,則對該值調用 consumer.accept
,不然什麼也不作。
因此對於上面的例子,咱們能夠修改成:
Optional<User> user = Optional.ofNullable(getUserById(id)); user.ifPresent(u -> System.out.println("Username is: " + u.getUsername()));
2.orElse
若是 Optional
中有值則將其返回,不然返回 orElse
方法傳入的參數。
User user = Optional .ofNullable(getUserById(id)) .orElse(new User(0, "Unknown")); System.out.println("Username is: " + user.getUsername());
3.orElseGet
orElseGet
與 orElse
方法的區別在於,orElseGet
方法傳入的參數爲一個 Supplier
接口的實現 —— 當 Optional
中有值的時候,返回值;當 Optional
中沒有值的時候,返回從該 Supplier
得到的值。
User user = Optional .ofNullable(getUserById(id)) .orElseGet(() -> new User(0, "Unknown")); System.out.println("Username is: " + user.getUsername());
4.orElseThrow
orElseThrow
與 orElse
方法的區別在於,orElseThrow
方法當 Optional
中有值的時候,返回值;沒有值的時候會拋出異常,拋出的異常由傳入的 exceptionSupplier 提供。
User user = Optional .ofNullable(getUserById(id)) .orElseThrow(() -> new EntityNotFoundException("id 爲 " + id + " 的用戶沒有找到"));
舉一個 orElseThrow
的用途:在 SpringMVC 的控制器中,咱們能夠配置統一處理各類異常。查詢某個實體時,若是數據庫中有對應的記錄便返回該記錄,不然就能夠拋出 EntityNotFoundException
,處理 EntityNotFoundException
的方法中咱們就給客戶端返回Http 狀態碼 404 和異常對應的信息 —— orElseThrow
完美的適用於這種場景。
@RequestMapping("/{id}") public User getUser(@PathVariable Integer id) { Optional<User> user = userService.getUserById(id); return user.orElseThrow(() -> new EntityNotFoundException("id 爲 " + id + " 的用戶不存在")); } @ExceptionHandler(EntityNotFoundException.class) public ResponseEntity<String> handleException(EntityNotFoundException ex) { return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND); }
5.map
若是當前 Optional
爲 Optional.empty
,則依舊返回 Optional.empty
;不然返回一個新的 Optional
,該 Optional
包含的是:函數 mapper 在以 value 做爲輸入時的輸出值。
String username = Optional.ofNullable(getUserById(id)) .map(user -> user.getUsername()) .orElse("Unknown") .ifPresent(name -> System.out.println("Username is: " + name));
並且咱們能夠屢次使用 map
操做:
Optional<String> username = Optional.ofNullable(getUserById(id)) .map(user -> user.getUsername()) .map(name -> name.toLowerCase()) .map(name -> name.replace('_', ' ')) .orElse("Unknown") .ifPresent(name -> System.out.println("Username is: " + name));
6.flatMap
flatMap
方法與 map
方法的區別在於,map
方法參數中的函數 mapper
輸出的是值,而後 map
方法會使用 Optional.ofNullable
將其包裝爲 Optional
;而 flatMap
要求參數中的函數 mapper
輸出的就是 Optional
。
Optional<String> username = Optional.ofNullable(getUserById(id)) .flatMap(user -> Optional.of(user.getUsername())) .flatMap(name -> Optional.of(name.toLowerCase())) .orElse("Unknown") .ifPresent(name -> System.out.println("Username is: " + name));
7.filter
filter
方法接受一個 Predicate
來對 Optional
中包含的值進行過濾,若是包含的值知足條件,那麼仍是返回這個 Optional
;不然返回 Optional.empty
。
Optional<String> username = Optional.ofNullable(getUserById(id)) .filter(user -> user.getId() < 10) .map(user -> user.getUsername()); .orElse("Unknown") .ifPresent(name -> System.out.println("Username is: " + name));
有了 Optional
,咱們即可以方便且優雅的在本身的代碼中處理 null
值,而再也不須要一昧經過容易忘記和麻煩的 if (object != null)
來判斷值不爲 null
。若是你的程序還在使用 Java8 以前的 JDK,能夠考慮引入 Google 的 Guava 庫 —— 事實上,早在 Java6 的年代,Guava 就提供了 Optional
的實現。
號外:Java9 對 Optional
的加強
即將在今年 7 月到來的 JDK9 中,在 Optional
類中添加了三個新的方法:
public Optional<T> or(Supplier<? extends Optional<? extends T>> supplier)
or
方法的做用是,若是一個 Optional
包含值,則返回本身;不然返回由參數 supplier 得到的 Optional
public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)
ifPresentOrElse
方法的用途是,若是一個 Optional
包含值,則對其包含的值調用函數 action,即 action.accept(value)
,這與 ifPresent
一致;與 ifPresent
方法的區別在於,ifPresentOrElse
還有第二個參數 emptyAction
—— 若是 Optional
不包含值,那麼 ifPresentOrElse
便會調用 emptyAction
,即 emptyAction.run()
public Stream<T> stream()
stream
方法的做用就是將 Optional
轉爲一個 Stream
,若是該 Optional
中包含值,那麼就返回包含這個值的 Stream
;不然返回一個空的 Stream
(Stream.empty()
)。
舉個例子,在 Java8,咱們會寫下面的代碼:
// 此處 getUserById 返回的是 Optional<User> public List<User> getUsers(Collection<Integer> userIds) { return userIds.stream() .map(this::getUserById) // 得到 Stream<Optional<User>> .filter(Optional::isPresent) // 去掉不包含值的 Optional,不然若是存在爲空的 Optional 下面的 get 會拋出異常 .map(Optional::get) // 變爲 Stream<User> .collect(Collectors.toList()); }
而有了 Optional.stream()
,咱們就能夠將其簡化爲:
public List<User> getUsers(Collection<Integer> userIds) { return userIds.stream() .map(this::getUserById) // 得到 Stream<Optional<User>> .flatMap(Optional::stream) // Stream 的 flatMap 方法將多個流合成一個流,若是 Optional 爲空則對應是空的 Stream,合併時會跳過 .collect(Collectors.toList()); }