今天我們深入探討Optional,水一下天數XD,在Java程式設計中,NullPointerException一直是困擾開發者的問題。
為解決這個問題,Java 8引入Optional類別,這是一個可以包含或不包含非null值的容器對象,Optional類別的設計目的是為更好地表達可能缺失的值,並提供一種更優雅的方式來處理可能為null的對象。
我們演示NullPointerException產生的場景:
public class NullPointerExampleDemo {
public static void main(String[] args) {
// 1. 訪問空對象的成員
String str = null;
System.out.println(str.length()); // NullPointerException
// 2. 訪問空數組
int[] arr = null;
System.out.println(arr[0]); // NullPointerException
// 3. 調用空對象的方法
NullPointerExampleDemo demo = null;
demo.someMethod(); // NullPointerException
// 4. 拆箱時遇到 null 值
Integer num = null;
int value = num; // NullPointerException
}
public void someMethod() {
System.out.println("This is some method");
}
}
Optional是一個泛型類,其中T代表包含的對象類型。Optional可以有兩種狀態:
使用Optional可以強制開發者考慮值不存在的情況,從而減少潛在的NullPointerException。
Java提供幾種創建Optional對象的方法:
創建一個空的Optional對象:
Optional<String> empty = Optional.empty();
創建一個包含非null值的Optional對象。如果傳入null,會拋出NullPointerException:
String name = "John";
Optional<String> optionalName = Optional.of(name);
// 以下程式碼會拋出NullPointerException
// Optional<String> nullOptional = Optional.of(null);
創建一個可能包含null值的Optional對象。如果傳入null,則返回一個空的Optional:
String name = "John";
Optional<String> optionalName = Optional.ofNullable(name);
String nullName = null;
Optional<String> nullOptional = Optional.ofNullable(nullName); // 不會拋出異常
Optional提供幾種方法來檢查是否包含值:
如果Optional包含值,返回true;否則返回false:
Optional<String> optionalName = Optional.of("John");
boolean isPresent = optionalName.isPresent(); // true
Optional<String> emptyOptional = Optional.empty();
boolean isEmpty = emptyOptional.isPresent(); // false
如果Optional為空,返回true;否則返回false:
Optional<String> optionalName = Optional.of("John");
boolean isEmpty = optionalName.isEmpty(); // false
Optional<String> emptyOptional = Optional.empty();
boolean isEmpty = emptyOptional.isEmpty(); // true
如果Optional包含值,返回該值;如果為空,拋出NoSuchElementException:
Optional<String> optionalName = Optional.of("John");
String name = optionalName.get(); // "John"
Optional<String> emptyOptional = Optional.empty();
// 以下程式碼會拋出NoSuchElementException
// String value = emptyOptional.get();
由於get()方法可能拋出異常,因此在使用時應該先檢查Optional是否包含值。
這兩個方法用於在Optional為空時提供默認值。
如果Optional包含值,則返回該值;否則返回指定的默認值:
String name = Optional.ofNullable(nullableName).orElse("Unknown");
與orElse()類似,但允許延遲生成默認值:
String name = Optional.ofNullable(nullableName).orElseGet(() -> "Unknown");
使用場景:當需要為可能為null的值提供默認值時。orElseGet()適用於默認值計算成本較高的情況。
如果Optional為空,拋出指定的異常:
String name = Optional.ofNullable(nullableName)
.orElseThrow(() -> new IllegalArgumentException("Name is required"));
使用場景:當值不存在時需要拋出特定異常。
如果Optional包含值,則執行指定的操作:
Optional.ofNullable(name).ifPresent(System.out::println);
如果Optional包含值,則執行指定的操作;否則執行另一個操作:
Optional.ofNullable(name).ifPresentOrElse(
System.out::println,
() -> System.out.println("Name is empty")
);
使用場景:根據Optional是否包含值執行不同的操作。
根據指定的條件過濾Optional中的值:
Optional<String> filteredName = Optional.of("John")
.filter(name -> name.length() > 3);
使用場景:當需要在處理Optional值之前先進行條件檢查。
如果Optional包含值,則應用映射函數:
Optional<Integer> nameLength = Optional.of("John")
.map(String::length);
flatMap(Function<? super T, Optional<U>> mapper)
類似map(),但用於處理返回Optional的映射函數:
Optional<String> upperName = Optional.of("john")
.flatMap(name -> Optional.of(name.toUpperCase()));
使用場景:當需要轉換Optional中的值或處理嵌套的Optional時。
提供一個替代的Optional:
Optional<String> result = Optional.empty()
.or(() -> Optional.of("Default"));
使用場景:當需要提供一個備選的Optional時。
將Optional轉換為Stream:
Stream<String> stream = Optional.of("value").stream();
使用場景:當需要將Optional與Stream API結合使用時。
昨天有提過,我們再複習一次。
從Java 9開始,Optional提供stream()方法,可以將Optional轉換為包含0或1個元素的Stream:
Optional<String> optional = Optional.of("value");
Stream<String> stream = optional.stream();
我們可以使用Optional.stream()方法來過濾掉Stream中的空值:
List<Optional<String>> listOfOptionals = Arrays.asList(
Optional.empty(), Optional.of("A"), Optional.empty(), Optional.of("B"));
List<String> filteredList = listOfOptionals.stream()
.flatMap(Optional::stream)
.collect(Collectors.toList());
// 結果: [A, B]
當我們需要對可能為null的值進行映射時,可以結合使用Optional和map操作:
List<String> names = Arrays.asList("John", null, "Jane");
List<String> uppercaseNames = names.stream()
.map(name -> Optional.ofNullable(name)
.map(String::toUpperCase)
.orElse(""))
.collect(Collectors.toList());
// 結果: [JOHN, , JANE]
當我們有一個返回Optional的方法,並且想在Stream中使用時,flatMap是一個很好的選擇:
class User {
Optional<Address> getAddress() { ... }
}
class Address {
Optional<String> getStreet() { ... }
}
List<User> users = ...;
List<String> streets = users.stream()
.map(User::getAddress)
.flatMap(Optional::stream)
.map(Address::getStreet)
.flatMap(Optional::stream)
.collect(Collectors.toList());
某些Stream的終端操作返回Optional,我們可以直接在這些結果上使用Optional的方法:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> max = numbers.stream()
.max(Integer::compare);
int maxValue = max.orElse(0);
當我們不確定Stream操作是否會產生結果時,可以使用返回Optional的方法:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Optional<String> firstLongName = names.stream()
.filter(name -> name.length() > 5)
.findFirst();
String longName = firstLongName.orElse("No long name found");
優先使用Stream API的方法: 如filter、map等,而不是在Stream操作中頻繁使用Optional的方法。
使用flatMap和Optional.stream()來扁平化嵌套的Optional。
在Stream的終端操作後使用Optional的方法來處理結果,而不是在中間操作中過度使用Optional。
避免創建包含Optional的集合,而是在需要時將Optional轉換為Stream。
// 好的做法
public Optional<User> findUserById(String id) { ... }
// 避免這樣做
public void processUser(Optional<User> user) { ... }
// 不推薦
if (user != null) {
String name = user.getName();
if (name != null) {
System.out.println(name.toUpperCase());
}
}
// 推薦
Optional.ofNullable(user)
.map(User::getName)
.map(String::toUpperCase)
.ifPresent(System.out::println);
String name = Optional.ofNullable(user)
.map(User::getName)
.orElse("Unknown");
User user = Optional.ofNullable(findUserById(id))
.orElseThrow(() -> new UserNotFoundException(id));
對於原始類型,使用專門的Optional類如OptionalInt、OptionalLong和OptionalDouble。
// 不推薦
Optional<Integer> count = Optional.of(5);
// 推薦
OptionalInt count = OptionalInt.of(5);
List<Optional<String>> optionals = ...;
List<String> result = optionals.stream()
.flatMap(Optional::stream)
.collect(Collectors.toList());
// 危險的做法
String name = optional.get(); // 可能拋出NoSuchElementException
// 安全的做法
String name = optional.orElse("Default");
不要為避免null就到處使用Optional。只在真正需要表示可能不存在的值時使用。
Optional會帶來一些性能開銷。在性能關鍵的程式碼中,可能需要權衡使用傳統的null檢查。
// orElse()總是會執行createExpensiveObject()
optional.orElse(createExpensiveObject());
// orElseGet()只在optional為空時執行lambda
optional.orElseGet(() -> createExpensiveObject());
// 不推薦
stream.map(Optional::of)
.filter(Optional::isPresent)
.map(Optional::get);
// 推薦
stream.filter(Objects::nonNull);
Optional不是為序列化而設計的,如果需要序列化,考慮使用自定義的可序列化包裝類。
Optional不是為作為字段使用而設計的。考慮使用@Nullable注解或設計模式來表示可選字段。
假設我們有一個用戶管理系統,需要處理可能不完整的用戶資料。
public class User {
private String name;
private Optional<String> email;
private Optional<Address> address;
// 構造函數、getter和setter
}
public class Address {
private String street;
private String city;
private Optional<String> zipCode;
// 構造函數、getter和setter
}
public class UserService {
public String getUpperCaseUserEmail(String userId) {
return findUserById(userId)
.flatMap(User::getEmail)
.map(String::toUpperCase)
.orElse("Email not provided");
}
public String getUserCityOrDefault(String userId, String defaultCity) {
return findUserById(userId)
.flatMap(User::getAddress)
.map(Address::getCity)
.orElse(defaultCity);
}
private Optional<User> findUserById(String userId) {
// 數據庫查詢邏輯
}
}
在處理應用程序配置時,Optional可以幫助我們處理可選的配置項。
public class ConfigManager {
private Map<String, String> config;
public Optional<String> getConfigValue(String key) {
return Optional.ofNullable(config.get(key));
}
public int getIntConfig(String key, int defaultValue) {
return getConfigValue(key)
.map(Integer::parseInt)
.orElse(defaultValue);
}
public List<String> getListConfig(String key) {
return getConfigValue(key)
.map(v -> Arrays.asList(v.split(",")))
.orElse(Collections.emptyList());
}
}
當與外部服務集成時,我們經常需要處理可能失敗的操作。
public class ExternalServiceClient {
public Optional<ExternalData> fetchData(String id) {
try {
// 調用外部服務的邏輯
ExternalData data = // ...
return Optional.ofNullable(data);
} catch (Exception e) {
logger.error("Error fetching data for id: " + id, e);
return Optional.empty();
}
}
}
public class DataProcessor {
private ExternalServiceClient client;
public ProcessResult processData(String id) {
return client.fetchData(id)
.map(this::processExternalData)
.orElseGet(this::handleMissingData);
}
private ProcessResult processExternalData(ExternalData data) {
// 處理數據的邏輯
}
private ProcessResult handleMissingData() {
// 處理數據缺失的邏輯
}
}
在數據轉換和驗證的場景中,Optional可以幫助我們處理可能無效的輸入。
public class DataValidator {
public Optional<Integer> parseAndValidateAge(String ageString) {
return Optional.ofNullable(ageString)
.filter(s -> !s.isEmpty())
.map(Integer::parseInt)
.filter(age -> age > 0 && age < 120);
}
public Optional<String> validateEmail(String email) {
return Optional.ofNullable(email)
.filter(e -> e.matches("^[A-Za-z0-9+_.-]+@(.+)$"));
}
}
public class UserRegistrationService {
private DataValidator validator;
public RegistrationResult registerUser(String name, String ageString, String email) {
Optional<Integer> age = validator.parseAndValidateAge(ageString);
Optional<String> validEmail = validator.validateEmail(email);
if (age.isPresent() && validEmail.isPresent()) {
// 進行用戶註冊
return RegistrationResult.success();
} else {
List<String> errors = new ArrayList<>();
age.ifPresentOrElse(
a -> {},
() -> errors.add("Invalid age")
);
validEmail.ifPresentOrElse(
e -> {},
() -> errors.add("Invalid email")
);
return RegistrationResult.failure(errors);
}
}
}
雖然Optional提供許多便利,但在使用時也需要考慮其對性能的影響。
Optional是一個包裝對象(Wrapper Object),因此使用會帶來一些額外的開銷:
考慮到性能因素,以下是一些使用Optional的建議:
讓我們通過一個簡單的性能測試來比較使用Optional和傳統null檢查的差異:
public class PerformanceTest {
private static final int ITERATIONS = 10_000_000;
public static void main(String[] args) {
testTraditionalNullCheck();
testOptional();
}
private static void testTraditionalNullCheck() {
long start = System.nanoTime();
for (int i = 0; i < ITERATIONS; i++) {
String result = getValueTraditional(i % 2 == 0 ? "Hello" : null);
if (result != null) {
result.toLowerCase();
}
}
long end = System.nanoTime();
System.out.println("Traditional null check: " + (end - start) / 1_000_000 + " ms");
}
private static void testOptional() {
long start = System.nanoTime();
for (int i = 0; i < ITERATIONS; i++) {
Optional<String> result = getValueOptional(i % 2 == 0 ? "Hello" : null);
result.map(String::toLowerCase);
}
long end = System.nanoTime();
System.out.println("Optional: " + (end - start) / 1_000_000 + " ms");
}
private static String getValueTraditional(String input) {
return input;
}
private static Optional<String> getValueOptional(String input) {
return Optional.ofNullable(input);
}
}
這個測試比較傳統null檢查和使用Optional處理可能為null的值的性能差異。在大多數情況下,你可能會發現使用Optional的版本稍慢一些。
如果你在使用Optional時遇到性能問題,可以考慮以下優化策略:
public Optional<String> getValueOptimized(boolean condition) {
if (condition) {
return Optional.of("Value");
}
return Optional.empty();
}
使用專門的Optional類: 對於原始類型,使用OptionalInt、OptionalLong和OptionalDouble可以減少裝箱拆箱的開銷。
在熱點程式碼中使用傳統方法: 對於頻繁執行的程式碼,考慮使用傳統的null檢查以提高性能。
使用Stream API時要謹慎: 在處理大量數據時,過度使用Optional可能會導致性能下降。考慮使用其他Stream操作來過濾null值。
// 避免
list.stream()
.map(Optional::ofNullable)
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
// 推薦
list.stream()
.filter(Objects::nonNull)
.collect(Collectors.toList());
本篇文章同步刊載: JYI.TW
筆者個人的網站: JUNYI