Java8新特性之五:Optional


  NullPointerException相信每個JAVA程序員都不陌生,是JAVA應用程序中最常見的異常。之前,Google Guava項目曾提出用Optional類來包裝對象從而解決NullPointerException。受此影響,JDK8的類中也引入了Optional類,在新版的SpringData Jpa和Spring Redis Data中都已實現了對該方法的支持。

1、Optional類

 1 /**
 2  * A container object which may or may not contain a non-null value.
 3  * If a value is present, {@code isPresent()} will return {@code true} and
 4  * {@code get()} will return the value.
 5  *
 6  * @since 1.8
 7  */
 8 public final class Optional<T> {
 9     /**
10      * Common instance for {@code empty()}.
11      */
12     private static final Optional<?> EMPTY = new Optional<>();
13 
14     /**
15      * If non-null, the value; if null, indicates no value is present
16      */
17     private final T value;
18 
19    // 其他省略
20 }

  該方法的注釋大致意思是:Optional是一個容器對象,它可能包含空值,也可能包含非空值。當屬性value被設置時,isPesent()方法將返回true,並且get()方法將返回這個值。

  該類支持泛型,即其屬性value可以是任何對象的實例。

2、Optional類的方法

    序號    

方法

方法說明
1
private Optional()
 無參構造,構造一個空Optional
2
private Optional(T value)
 根據傳入的非空value構建Optional
3
public static<T> Optional<T> empty()
返回一個空的Optional,該實例的value為空
4  
public static <T> Optional<T> of(T value)
根據傳入的非空value構建Optional,與Optional(T value)方法作用相同
5  
public static <T> Optional<T> ofNullable(T value)

 與of(T value)方法不同的是,ofNullable(T value)允許你傳入一個空的value,

當傳入的是空值時其創建一個空Optional,當傳入的value非空時,與of()作用相同

6  
public T get()
 返回Optional的值,如果容器為空,則拋出NoSuchElementException異常
7  
public boolean isPresent()
 判斷當家Optional是否已設置了值
8  
public void ifPresent(Consumer<? super T> consumer)
 判斷當家Optional是否已設置了值,如果有值,則調用Consumer函數式接口進行處理
9  
public Optional<T> filter(Predicate<? super T> predicate)
 如果設置了值,且滿足Predicate的判斷條件,則返回該Optional,否則返回一個空的Optional
10  
public<U> Optional<U> map(Function<? super T, ? extends U> mapper)

 如果Optional設置了value,則調用Function對值進行處理,並返回包含處理后值的Optional,否則返回空Optional

11  
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper)
 與map()方法類型,不同的是它的mapper結果已經是一個Optional,不需要再對結果進行包裝
12  
public T orElse(T other)
 如果Optional值不為空,則返回該值,否則返回other 
13
public T orElseGet(Supplier<? extends T> other)
如果Optional值不為空,則返回該值,否則根據other另外生成一個
14
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)
throws X
如果Optional值不為空,則返回該值,否則通過supplier拋出一個異常

3、Optional類的方法舉例

  《Java 8 Features Tutorial – The ULTIMATE Guide》中給出了兩個簡單的例子,我們從這兩個例子入手來簡單了解一下Optional容器的使用。

  示例一:

1 public static void main(String[] args) {
2   Optional< String > fullName = Optional.ofNullable( null );
3   System.out.println( "Full Name is set? " + fullName.isPresent() );
4   System.out.println( "Full Name: " + fullName.orElseGet( () -> "[none]" ) );
5   System.out.println( fullName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
6 }

  運行結果:

Full Name is set? false
Full Name: [none]
Hey Stranger!  

  說明:

  ifPresent()方法當Optional實例的值非空時返回true,否則返回false; 

  orElseGet()方法當Optional包含非空值時返回該值,否則通過接收的function生成一個默認的;

  map()方法轉換當前Optional的值,並返回一個新的Optional實例;

  orElse()方法與orElseGet方法相似,不同的是orElse()直接返回傳入的默認值。

 

  示例二:修改示例一,使其生成一個非空值的Optional實例

1 Optional< String > firstName = Optional.of( "Tom" );
2 System.out.println( "First Name is set? " + firstName.isPresent() );
3 System.out.println( "First Name: " + firstName.orElseGet( () -> "[none]" ) );
4 System.out.println( firstName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );

  輸出結果:

First Name is set? true
First Name: Tom
Hey Tom!

  可以清晰地看出與示例一的區別。這不但簡潔了我們的代碼,而且使我們的代碼更便於閱讀。

  下面看一下例子中使用到的幾個方法的源碼:

  1)、of

1 public static <T> Optional<T> of(T value) {
2   return new Optional<>(value);
3}

  2)、isPresent

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

  3)、orElseGet

1 public T orElseGet(Supplier<? extends T> other) {
2     return value != null ? value : other.get();
3 }

  4)、orElse

1 public T orElse(T other) {
2     return value != null ? value : other;
3 }

  其他方法源碼,讀者可以去Optional源碼中查看。

4、使用Optional避免空指針

  在我們日常開發過程中不可避免地會遇到空指針問題,在以前,出現空指針問題,我們通常需要進行調試等方式才能最終定位到具體位置,尤其是在分布式系統服務之間的調用,問題更難定位。在使用Optional后,我們可以將接受到的參數對象進行包裝,比如,訂單服務要調用商品服務的一個接口,並將商品信息通過參數傳入,這時候,傳入的商品參數可能直接傳入的就是null,這時,商品方法可以使用Optional.of(T)對傳入的對象進行包裝,如果T為空,則會直接拋出空指針異常,我們看到異常信息就能立即知道發生空指針的原因是參數T為空;或者,當傳入的參數為空時,我們可以使用Optional.orElse()或Optional.orElseGet()方法生成一個默認的實例,再進行后續的操作。

  下面再看個具體例子:在User類中有個Address類,在Address類中有個Street類,Street類中有streetName屬性,現在的需求是:根據傳入的User實例,獲取對應的streetName,如果User為null或Address為null或Street為null,返回“nothing found”,否則返回對應的streetName。

  實現一:

1 @Data
2 public class User {
3     private String name;
4     private Integer age;
5     private Address address;
6 }
1 @Data
2 public class Address {
3     private Street street;
4 }
1 @Data
2 public class Street {
3     private String streetName;
4     private Integer streetNo;
5 }
 1 public String getUserSteetName(User user) {
 2 
 3     if(null != user) {
 4 
 5         Address address = user.getAddress();
 6 
 7         if(null != address) {
 8 
 9             Street street = address.getStreet();
10 
11             if(null != street) {
12                 return street.getStreetName();
13             }
14         }
15     }
16 
17     return "nothing found";
18 }

  實現二,使用Optional:

   在實現一中明顯的問題是if判斷層級太深,下面復用Optional改寫:

1 @Data
2 public class User {
3     private String name;
4     private Integer age;
5     private Optional<Address> address = Optional.empty();
6 }
1 @Data
2 public class Address {
3     private Optional<Street> street = Optional.empty();
4 }
1 @Data
2 public class Street {
3     private String streetName;
4     private Integer streetNo;
5 }
1 public String getUserSteetName(User user) {
2 
3     Optional<User> userOptional = Optional.ofNullable(user);
4     final String streetName = userOptional.orElse(new User()).getAddress().orElse(new Address()).getStreet().orElse(new Street()).getStreetName();
5     return StringUtils.isEmpty(streetName) ? "nothing found" : streetName;
6 }

  利用orElse()方法給定默認值的方式確保不會報空指針問題問題,同時也能實現需求。


免責聲明!

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



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