在 Java 中使用 Optional
一、簡介
在本教程中,我們將了解 Java 中Optional
類的用途以及在構建應用程序時使用它的一些優勢。
2. Java 中Optional<T>
的用途
Optional
是一個表示某物存在或不存在的類。從技術上講, Optional
是泛型T
的包裝類,如果T
為null
,則Optional
實例為空。否則,它已滿。
根據 Java 11 文檔, Optional
的目的是提供一種返回類型,該類型可以在返回null
可能導致意外錯誤(例如臭名昭著的NullPointerException
)的情況下表示值的缺失。
2.1.有用的方法
Optional
類提供了有用的方法來幫助我們使用該 API。對於本文而言,重要的是of(), orElse(),
和empty()
方法:
-
of(T value)
返回一個Optional
的實例,裡面有一個值 -
orElse(T other)
返回Optional
中的值,否則返回other
- 最後,
empty()
返回Optional
的一個空實例
我們將看到這些方法的實際應用,以構建返回Optional
的代碼和使用它的代碼。
3. Optional
的優勢
我們已經了解了Optional
的用途及其一些方法。但是,我們怎樣才能從我們的課程中受益呢?在本節中,我們將看到一些使用它的方法,這些方法可以幫助我們創建清晰而健壯的 API。
3.1. Optional
與null
在創建Optional
類之前,我們必須使用null
來表示值的缺失。該語言並沒有要求我們正確處理null
情況。也就是說, null
檢查有時是必要的,但不是強制的。因此,創建返回null
的方法被證明是一種產生意外運行時錯誤(如NullPointerException
)的方法。
另一方面,應始終在編譯時正確處理Optional
的實例以獲取其中的值。這種在編譯時處理Optional
的義務導致更少的意外NullPointerException
。
讓我們嘗試一個模擬User
數據庫的例子:
public class User {
public User(String id, String name) {
this.id = id;
this.name = name;
}
private String id;
private String name;
public String getName() {
return name;
}
public String getId() {
return id;
}
}
讓我們還定義存儲庫類,如果找到則返回一個User
。否則,它返回null
:
public class UserRepositoryWithNull {
private final List<User> dbUsers = Arrays.asList(new User("1", "John"), new User("2", "Maria"), new User("3", "Daniel"));
public User findById(String id) {
for (User u : dbUsers) {
if (u.getId().equals(id)) {
return u;
}
}
return null;
}
}
現在,我們將編寫一個單元測試,以顯示如果我們不使用null
檢查來解決null
,代碼將如何因NullPointerException
而中斷:
@Test
public void givenNonExistentUserId_whenSearchForUser_andNoNullCheck_thenThrowException() {
UserRepositoryWithNull userRepositoryWithNull = new UserRepositoryWithNull();
String nonExistentUserId = "4";
assertThrows(NullPointerException.class, () -> {
System.out.println("User name: " + userRepositoryWithNull.findById(nonExistentUserId)
.getName());
});
}
Java 允許我們使用findById()
返回的對像中的getName()
方法,而無需進行null
檢查。那樣的話,我們只能在運行時發現問題。
為了避免這種情況,我們可以創建另一個存儲庫,如果我們找到一個User,
那麼我們返回一個完整的Optional
。否則,我們返回一個空的。讓我們在實踐中看看:
public class UserRepositoryWithOptional {
private final List<User> dbUsers = Arrays.asList(new User("1", "John"), new User("2", "Maria"), new User("3", "Daniel"));
public Optional<User> findById(String id) {
for (User u : dbUsers) {
if (u.getId().equals(id)) {
return Optional.of(u);
}
}
return Optional.empty();
}
}
現在,當我們重寫我們的單元測試時,我們看到當我們找不到任何User
時,我們必須如何首先處理Optional
以獲得它的值:
@Test
public void givenNonExistentUserId_whenSearchForUser_thenOptionalShouldBeTreatedProperly() {
UserRepositoryWithOptional userRepositoryWithOptional = new UserRepositoryWithOptional();
String nonExistentUserId = "4";
String userName = userRepositoryWithOptional.findById(nonExistentUserId)
.orElse(new User("0", "admin"))
.getName();
assertEquals("admin", userName);
}
在上面的例子中,我們沒有找到任何User
,所以我們可以使用orElse()
方法返回一個默認用戶。要獲得它的價值,必須在編譯時正確對待Optional
。這樣,我們就可以減少運行時的意外錯誤。
除了使用orElseThrow()
orElse()
方法提供默認值之外,我們還可以使用另外兩個選項,即使用 orElseThrow() 拋出異常或使用orElseGet()
調用Supplier
函數。
3.2.設計明確的意圖 API
正如我們之前所討論的, null
被廣泛用於表示無。但是, null
的含義只有創建 API 的人才能清楚。瀏覽該 API 的其他開發人員可能會發現null
的不同含義。
可能名稱“Optional”是Optional
在構建我們的 API 時成為有用工具的主要原因。方法返回中的Optional
提供了我們應該從該方法中期望的明確意圖:它返回一些東西或什麼都不返回。不需要文檔來解釋該方法的返回類型。代碼會自行解釋。
使用返回null
的存儲庫,我們可能會以最糟糕的方式發現null
表示在數據庫中找不到User
。或者它可能代表其他事情,比如連接數據庫時出錯,或者對象尚未初始化。很難知道。
另一方面,使用返回Optional
實例的存儲庫可以通過僅查看我們可能會或可能不會在數據庫中找到用戶的方法簽名來明確。
設計清晰的 API 的一個重要實踐是永遠不要返回null
Optional
。方法應始終使用static
方法返回Optional
的有效實例。
3.3.聲明式編程
使用Optional
類的另一個很好的理由是能夠使用一系列流暢的方法。它提供了一個類似於集合中的stream()
的“偽流” ,但只有一個值。這意味著我們可以在其中的值上調用map()
和filter()
之類的方法。這有助於創建更多聲明式程序,而不是命令式程序。
假設要求是如果名稱以字母“M”開頭,則將User
name
的大小寫更改為大寫。
首先,讓我們看看命令式的方式,使用不返回Optional
的存儲庫:
@Test
public void givenExistentUserId_whenFoundUserWithNameStartingWithMInRepositoryUsingNull_thenNameShouldBeUpperCased() {
UserRepositoryWithNull userRepositoryWithNull = new UserRepositoryWithNull();
User user = userRepositoryWithNull.findById("2");
String upperCasedName = "";
if (user != null) {
if (user.getName().startsWith("M")) {
upperCasedName = user.getName().toUpperCase();
}
}
assertEquals("MARIA", upperCasedName);
}
現在,讓我們看一下聲明方式,使用存儲庫的Optional
版本:
@Test
public void givenExistentUserId_whenFoundUserWithNameStartingWithMInRepositoryUsingOptional_thenNameShouldBeUpperCased() {
UserRepositoryWithOptional userRepositoryWithOptional = new UserRepositoryWithOptional();
String upperCasedName = userRepositoryWithOptional.findById("2")
.filter(u -> u.getName().startsWith("M"))
.map(u -> u.getName().toUpperCase())
.orElse("");
assertEquals("MARIA", upperCasedName);
}
命令式方式需要嵌套兩個if
語句來判斷對像是否不為null
和過濾用戶名。如果未找到User
,則大寫字符串保持為空。
在聲明方式中,我們使用 lambda 表達式過濾名稱並將大寫函數映射到找到的User
。如果未找到User
,我們將使用orElse()
返回一個空字符串。
我們使用哪一個仍然是一個偏好問題。他們都達到相同的結果。命令式方式需要更多挖掘才能理解代碼的含義。例如,如果我們在第一個或第二個if
語句中添加更多邏輯,它可能會對該代碼的意圖造成一些混淆。在這種情況下,聲明式編程明確了代碼的意圖:返回大寫的名稱,否則返回空字符串。
4。結論
在本文中,我們了解了Optional
類的用途以及如何有效地使用它來設計清晰而健壯的 API。
與往常一樣,該示例的源代碼可在 GitHub 上獲得。