【譯】你的Java8 Optional指南

原文地址html

Java程序員面臨的最多見異常之一是NullPointerException。 JVM會在運行時將該異常做爲運行時異常拋出。java

衆所周知,當程序須要一個對象可是卻找到一個null值時,就會拋出NullPointerException。對空指針異常進行處理是Java程序員最容易忽略的場景之一。程序員

在繼續執行常規業務邏輯以前,須要在應用程序中處理空指針異常,以免在運行時產生空指針異常。這致使了沒必要要的空指針檢測代碼的存在。數據庫

爲了在Java中處理這些用於進行空指針檢測的模板代碼,因此Java 8中引入了新類型Optional<T>api

在沒有引入Optional以前會有什麼問題?

據Oracle說,Java 8中引入的Optional做爲一個容器類型,用於包裝值不存在或爲null的狀況。 Optional是java.util包中一個具備final修飾符的類。oracle

咱們來看看沒有Optional會遇到哪些問題。app

假設在咱們的程序中具備如下方法。此方法使用員工的ID從數據庫中檢索員工的詳細信息:ide

Employee findEmployee(String id) {
 ...
};
複製代碼

假設在使用上述方法時所提供的ID在數據庫中不存在。而後,該方法將返回null值。如今,若是咱們已經編寫了下面的代碼:函數

Employee employee = findEmployee("1234");
System.out.println("Employee's Name = " + employee.getName());
複製代碼

上面的代碼將在運行時拋出NullPointerException異常,由於程序員在使用該值以前沒有對它進行空指針檢測。學習

Java 8的Optional提供了怎樣的解決方案?

如今,讓咱們看看Java 8引入的Optional類型將如何解決上述問題並幫助消除NullPointerException異常。

如下是上述代碼修改事後的代碼:

Optional<Employee> findEmployee(String id) {
 ...
};
複製代碼

在上面的代碼中,咱們將返回類型修改成Optional<Employee>來向調用者代表與給定ID的對應的員工可能不存在。

如今,須要在調用方中明確表達這一事實。

調用者應該編寫以下代碼:

Optional <Employee> optional = findEmployee("1234");
optional.ifPresent(employee -> {
 System.out.println("Employee name is " + employee.getName());
})
複製代碼

你能夠看到咱們在上述代碼的第一行中建立了一個Optional對象。如今,咱們可使用Optional對象上面的各類方法。

以上代碼段中的ifPresent()方法僅在員工存在的狀況下才調用提供的lambda表達式。 若是員工不存在,則什麼事都不會作。

Optional的優勢

如下列出的是使用Optional的一些優勢:

  • 程序在運行時不會拋出NullPointerException異常。
  • 在程序中再也不須要空指針檢測。
  • 容易開發乾淨整潔的API。

Optional類的方法

Methods Description
public static Optional empty() This method returns an empty Optional object. No value is present for this Optional.
public static Optional of(T value) This method returns an Optional with the specified value that is not null.
public static Optional ofNullable(T value) This method returns an Optional describing the specified value if the value is non-null; otherwise, it returns an empty Optional.
public T get() If a value is present in this Optional, then it returns the value. Otherwise, it throws NoSuchElementException.
public boolean isPresent() This method returns a true value if there is a value present. Otherwise, it returns false.
public void ifPresent(Consumer<? super T> consumer) If a value is present, then the consumer with the provided value is invoked. Otherwise, it does nothing.
public Optional filter(Predicate<? super T> predicate) If a value is present and it also matches the given predicate, then it returns an Optional describing the value. Otherwise, it returns an empty Optional.
public Optional map(Function<? super T,? extends U> mapper) If a value is present, then the mapping function is applied, and if the result is not a null value, then it returns an Optional describing the result. Otherwise, it returns an empty Optional.
public Optional flatMap(Function<? super T,Optional mapper) If the value is present, then it applies the provided Optional-bearing mapping function to it and it returns that result. Otherwise, it returns an empty Optional.
public T orElse(T other) This method returns the value if present; otherwise, it returns other.
public T orElseGet(Supplier<? extends T> other) This method returns the value if present. Otherwise, it invokes other and returns the result of the invocation.
public T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X extends Throwable If the value is present, this method returns the contained value. Otherwise, it throws an exception to be created by the provided supplier.
public boolean equals(Object obj) This method is used for indicating whether some other object is 「equal to」 this Optional or not.
public int hashCode() This method returns the hash code value of the present value if it exists. Otherwise, it returns 0 (zero) .
public String toString() This method is simply used to return a non-empty string representation of the Optional, which is suitable for debugging.

建立一個Optional對象

在本節中,咱們將研究用於建立Optional對象的方法:

1. 建立一個包含空值的Optional對象

下面的代碼演示瞭如何建立一個具備空值的Optional對象。它用來表示值爲null的狀況。

Optional <Employee> employee = Optional.empty();
複製代碼

2. 建立一個具備非空值的Optional對象

下面的代碼展現瞭如何建立一個具備非空值的Optional對象。

Employee employee = new Employee("1234", "TechBlogStation");
Optional <Employee> optional = Optional.of(employee);
複製代碼

請注意,若是爲Optional.of()的參數提供了null,則它將當即拋出NullPointerException異常,因此建立Optional對象將會失敗。

檢查Optional對象中是否存在非空值

如今,讓咱們學習經過不一樣的方法檢查Optional對象中是否存在非空值:

1. isPresent()方法

若是Optional對象中存在非空值,則方法isPresent()返回true,不然,它返回false。

if (optional.isPresent()) {
  // optional中有值
  System.out.println("Value - " + optional.get());
} else {
  // optional中沒有值
  System.out.println("Optional is empty");
}    

複製代碼

2.ifPresent()方法

在方法ifPresent()中,咱們將傳遞一個Customer函數過去。僅當Optional對象中存在非空值時,纔會執行該Consumer函數。

若是Optional對象中存放的是空值,則不執行任何操做:

optional.ifPresent(value -> {   
  System.out.println("Value present - " + value); 
});
複製代碼

在上面的代碼中,咱們提供了lambda函數做爲ifPresent()方法的參數。

使用get()方法從Optional中取出值

Optional的get()方法僅用於從Optional對象取出值。若是Optional對象中保存的是一個空值,則將引起NoSuchElementException異常。

Employee employee = optional.get()
複製代碼

在上述代碼中,若是optional對象中是個保存的是空值,則會拋出異常,所以建議在使用get()方法以前,咱們應該首先檢查optional中是否有值。

使用Optional的orElse()方法返回默認值

若是Optional對象中保存的是空值,則返回調用orElse()方法所傳入的參數做爲默認值。若是其中保存的是非空值,則返回其中保存的值。

請參見下面的示例:

// 使用三目表達式返回默認值
User finalEmployee = (employee != null) ? employee : new Employee("0", "Unknown Employee");
複製代碼

如今,使用Optional的orElse()方法實現與上面相同的邏輯:

// 使用orElse()方法返回默認值
User finalEmployee = optional.orElse(new Employee("0", "Unknown Employee"));
複製代碼

使用Optional的orElseGet()方法返回默認值

咱們已經知道,若是Optional對象爲空,則方法orElse()直接返回調用時傳入的參數做爲默認值,而orElseGet()方法接受一個 Supplier做爲參數,而且當Optional對象中保存的是空值時調用Supplier。

Supplier返回的結果將成爲Optional的默認值。

User finalEmployee = optional.orElseGet(() -> {
 return new Employee("0", "Unknown Employee");
});
複製代碼

若是Optional中存放的是空值,則拋出異常

若是Optional對象爲空,則可使用orElseThrow()方法拋出一個異常。

它能夠用於REST API中指定的對象不存在的狀況。您可使用此方法拋出自定義異常,例如ResourceNotFound()等:

@GetMapping("/employees/{id}")
public User getEmployee(@PathVariable("id") String id) {
 return employeeRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Employee not found with id " + id););
}
複製代碼

使用Optional的Filter()方法過濾值

假設您有一個Employee的Optional對象。如今,你正在檢查員工的性別並相應地調用一個函數。

下面是這樣作的舊方法:

if(employee != null && employee.getGender().equalsIgnoreCase("MALE")) {
	// 調用函數
}
複製代碼

如今,讓咱們看看如何使用Optional的filter()方法來達到此目的:

optional.filter(user -> employee.getGender().equalsIgnoreCase("MALE")) .ifPresent(() -> {
    // 你的函數
})
複製代碼

filter()方法將一個謂詞函數做爲參數。若是Optional對象中包含一個非空值而且該值與提供的謂詞匹配,則此方法將返回一個包含該值的Optional對象。

不然,此方法返回一個包含空值的Optional對象。

使用map()提取與轉換值

假設咱們有一個場景,咱們要提取僱員的地址,還要根據指定條件打印出該地址。

考慮如下示例:

咱們在Employee類中有getAddress()方法:

Address getAddress() {
	return this.address;
}
複製代碼

如下是實現上述需求的典型方式:

if (employee != null) {
    Address address = employee.getAddress();
    if (address != null && address.getCountry().equalsIgnoreCase("USA")) {
        System.out.println("Employee belongs to USA");
    }
}
複製代碼

如今看看如何使用Optional的map()方法實現同樣的功能:

userOptional.map(Employee::getAddress).filter(address -> address.getCountry().equalsIgnoreCase("USA")).ifPresent(() -> {
 System.out.println("Employee belongs to USA");
});
複製代碼

與之前的方法相比,以上代碼可讀性強、簡潔與高效。

讓咱們更詳細地分析一下代碼:

// 使用map()方法提取地址
Optional<Address> addressOptional = employeeOptional.map(Employee::getAddress) 

// 過濾出來自USA的員工
Optional<Address> usaAddressOptional = addressOptional.filter(address -> address.getCountry().equalsIgnoreCase("USA")); 

// 若是員工來自USA則輸出一些信息
usaAddressOptional.ifPresent(() -> {
  System.out.println("Employee belongs to USA");
});
複製代碼

在上述代碼段中,出現如下任意一種狀況,方法map()返回一個包含空值的Optional對象:

  1. 若是employeeOptional中不存在員工。
  2. 若是員工存在,但方法getAddress()返回null。

不然,返回包含員工地址的Optional<Address>對象。

使用flatMap()級聯Optional

如今,讓咱們再次考慮上述與map()方法相關的示例。

你能夠看到即便Employee地址能夠爲null,咱們也沒有使用Optional<Address>做爲getAddress()方法的返回類型。

若是getAddres()方法的返回類型爲Optional<Address>,則下面這一行代碼將會出現問題:

Optional<Address> addressOptional = employeeOptional.map(Employee::getAddress)
複製代碼

因爲getAddress()方法返回Optional <Address>,所以employeeOptional.map()的返回類型是Optional<Optional<Address>>

下面是對於這種狀況的演示:

Optional<Optional<Address>> addressOptional = employeeOptional.map(Employee::getAddress)
複製代碼

這種狀況下,咱們嵌套了Optional,可是咱們不但願這樣作,因此咱們如今可使用flatMap()方法解決這個問題:

Optional<Address> addressOptional = employeeOptional.flatMap(Employee::getAddress)
複製代碼

請注意,若是您的傳遞給map()方法的函數返回一個Optional對象,則應該使用flatMap()方法替換掉map()方法,從而獲取到展開後的結果。

總結

咱們已經學習了Java 8中的Optional是什麼,它的優勢以及在Java中經過使用Optional解決問題。此外,經過一些示例,咱們可以更好地明白Java 8中Optional類的一些方法。

謝謝閱讀!

進一步閱讀

Java 8 Optional Uses and Best Practices

26 Reasons Why Using Optional Correctly Is Not Optional

Java 8 Optional: Handling Nulls Properly

相關文章
相關標籤/搜索