Java Optional

Optional 是 Java 8 引入的容器類別,用來處理可能為 null 的值,避免 NullPointerException。

建立 Optional

// 包含值
Optional<String> opt1 = Optional.of("Hello");

// 空的 Optional
Optional<String> opt2 = Optional.empty();

// 值可能為 null
String str = null;
Optional<String> opt3 = Optional.ofNullable(str);  // 等同於 empty()
Optional<String> opt4 = Optional.ofNullable("Hello");  // 等同於 of()
Optional.of(null) 會拋出 NullPointerException。

檢查和取值

Optional<String> opt = Optional.of("Hello");

// 檢查是否有值
if (opt.isPresent()) {
    System.out.println(opt.get());
}

// 如果有值就執行
opt.ifPresent(s -> System.out.println(s));
opt.ifPresent(System.out::println);

// Java 9+ 如果有值或沒有值
opt.ifPresentOrElse(
    s -> System.out.println("有值:" + s),
    () -> System.out.println("沒有值")
);

取得值或預設值

Optional<String> opt = Optional.empty();

// orElse:取得值或預設值
String result1 = opt.orElse("預設值");  // "預設值"

// orElseGet:取得值或計算預設值(惰性)
String result2 = opt.orElseGet(() -> "計算的預設值");

// orElseThrow:取得值或拋出例外
String result3 = opt.orElseThrow();  // NoSuchElementException
String result4 = opt.orElseThrow(() -> new RuntimeException("沒有值"));

orElse vs orElseGet

// orElse:總是會執行預設值計算
String result1 = opt.orElse(expensiveOperation());  // 即使有值也會執行

// orElseGet:只在需要時才計算
String result2 = opt.orElseGet(() -> expensiveOperation());  // 有值不會執行

轉換

map

Optional<String> opt = Optional.of("Hello");

Optional<Integer> length = opt.map(String::length);  // Optional[5]
Optional<String> upper = opt.map(String::toUpperCase);  // Optional[HELLO]

// 空的 Optional
Optional<String> empty = Optional.empty();
Optional<Integer> emptyLen = empty.map(String::length);  // Optional.empty

flatMap

用於回傳 Optional 的函數:

Optional<String> opt = Optional.of("Hello");

// map 會產生 Optional<Optional<Integer>>
Optional<Optional<Integer>> nested = opt.map(s -> Optional.of(s.length()));

// flatMap 會攤平
Optional<Integer> flat = opt.flatMap(s -> Optional.of(s.length()));

過濾

Optional<Integer> opt = Optional.of(10);

Optional<Integer> filtered = opt.filter(n -> n > 5);  // Optional[10]
Optional<Integer> filtered2 = opt.filter(n -> n > 20);  // Optional.empty

實際應用

避免 null 檢查

// 傳統方式
public String getCity(Person person) {
    if (person != null) {
        Address address = person.getAddress();
        if (address != null) {
            return address.getCity();
        }
    }
    return "Unknown";
}

// 使用 Optional
public String getCityOptional(Person person) {
    return Optional.ofNullable(person)
        .map(Person::getAddress)
        .map(Address::getCity)
        .orElse("Unknown");
}

作為回傳值

public Optional<User> findUserById(String id) {
    User user = database.find(id);
    return Optional.ofNullable(user);
}

// 使用
Optional<User> user = findUserById("123");
user.ifPresent(u -> System.out.println(u.getName()));
String name = user.map(User::getName).orElse("Anonymous");

Stream 整合

List<String> names = Arrays.asList("Alice", null, "Bob", null, "Charlie");

// 過濾 null
List<String> nonNull = names.stream()
    .filter(Objects::nonNull)
    .collect(Collectors.toList());

// 使用 Optional
List<String> result = names.stream()
    .map(Optional::ofNullable)
    .filter(Optional::isPresent)
    .map(Optional::get)
    .collect(Collectors.toList());

// Java 9+ flatMap + stream
List<String> result2 = names.stream()
    .flatMap(s -> Optional.ofNullable(s).stream())
    .collect(Collectors.toList());

Optional 的方法總覽

方法說明
of(value)建立(值不能為 null)
ofNullable(value)建立(值可以為 null)
empty()建立空的 Optional
isPresent()是否有值
isEmpty()是否為空(Java 11+)
get()取得值
orElse(default)值或預設值
orElseGet(supplier)值或計算預設值
orElseThrow()值或拋出例外
ifPresent(consumer)如果有值就執行
map(function)轉換
flatMap(function)轉換並攤平
filter(predicate)過濾
or(supplier)值或另一個 Optional(Java 9+)
stream()轉為 Stream(Java 9+)

最佳實踐

  1. 不要作為欄位:Optional 適合作為回傳值
  2. 不要作為參數:使用多載方法代替
  3. 不要用 get() 而沒有 isPresent()
  4. 優先使用 orElse 或 map
// 不好
Optional<String> opt = getOptional();
if (opt.isPresent()) {
    return opt.get();
}
return "default";

// 好
return getOptional().orElse("default");