Optional 是 Java 8 引入的概念,初看很簡單,其實用法超深。一開始很多人覺得 Optional 很煩,後來用對了才發現「真香」。最關鍵是理解它的三段式結構。

Optional 的三段式結構

Optional 的用法可以分成三段:頭部(建立)→ 身體(轉換)→ 尾巴(取值)

1
2
建立        轉換(0~多個)       取值
Optional → filter/map/flatMap → orElse/ifPresent

頭部:建立 Optional

of() - 確定非 null

1
2
3
4
5
6
7
User user = new User("Alice");

// 用 of() 當你確定值一定不為 null
Optional<User> opt = Optional.of(user);

// 如果值是 null,直接拋 NullPointerException
Optional<User> opt2 = Optional.of(null); // ❌ 拋 exception

ofNullable() - 可能 null

1
2
3
4
5
6
User user = getUserFromDB(123);  // 可能回傳 null

// 用 ofNullable 當不確定值是否為 null
Optional<User> opt = Optional.ofNullable(user);

// 如果 user 是 null,opt 就是 empty Optional,不會拋 exception

empty() - 明確的空

1
2
// 當你要返回「明確沒有值」時
Optional<User> opt = Optional.empty();

身體:轉換資料

filter() - 條件篩選

1
2
3
4
5
6
7
8
9
10
Optional<User> user = Optional.ofNullable(getUser());

// filter:條件滿足則保留,不滿足變成 empty
Optional<User> adult = user
.filter(u -> u.getAge() >= 18);

// 等等於
if (user.isPresent() && user.get().getAge() >= 18) {
// 有值
}

實務例子:

1
2
3
4
public Optional<User> findActiveUser(Long userId) {
return Optional.ofNullable(userRepository.findById(userId))
.filter(u -> u.isActive()); // 只返回活躍的使用者
}

map() - 轉換值

1
2
3
4
5
6
7
8
9
10
11
12
13
Optional<User> user = Optional.ofNullable(getUser());

// map:如果有值,轉換它;沒值則保持 empty
Optional<String> name = user
.map(u -> u.getName());

// 等等於
Optional<String> name;
if (user.isPresent()) {
name = Optional.of(user.get().getName());
} else {
name = Optional.empty();
}

map 會自動處理 null:

1
2
3
4
5
6
Optional<User> user = Optional.ofNullable(getUser());

Optional<String> email = user
.map(u -> u.getEmail()); // 如果 getEmail() 回傳 null,變成 empty

// 不用自己檢查 null

flatMap() - 值本身是 Optional

當轉換函式本身回傳 Optional 時,用 flatMap:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public Optional<Address> getAddress(User user) {
// 這個方法本身回傳 Optional
return Optional.ofNullable(user.getAddress());
}

Optional<User> user = Optional.ofNullable(getUser());

// 錯誤做法 - map 會包裝兩層 Optional
Optional<Optional<Address>> address1 = user
.map(u -> getAddress(u)); // Optional<Optional<Address>>

// 正確做法 - flatMap 會自動展開
Optional<Address> address = user
.flatMap(u -> getAddress(u)); // Optional<Address>

flatMap 原理:

1
2
3
4
5
6
// map(f) 的邏輯:
// if (value.present) { return Optional.of(f(value)); } else { return empty; }

// flatMap(f) 的邏輯:
// if (value.present) { return f(value); } else { return empty; }
// 注意:f 本身回傳 Optional,flatMap 不會再包一層

or() - 替代來源(Java 9+)

1
2
3
4
5
6
7
8
9
10
11
12
13
Optional<User> primaryUser = findUserInDB(123);
Optional<User> backupUser = findUserInCache(123);

// 如果 primaryUser 是 empty,用 backupUser
Optional<User> user = primaryUser.or(() -> backupUser);

// 等等於
Optional<User> user;
if (primaryUser.isPresent()) {
user = primaryUser;
} else {
user = backupUser;
}

stream() - 轉成 Stream(Java 9+)

1
2
3
4
5
6
7
8
9
10
11
12
13
List<User> users = Arrays.asList(user1, user2);

users.stream()
.map(u -> findUserProfile(u.getId())) // Optional<Profile>
.flatMap(Optional::stream) // 展開 Optional
.forEach(System.out::println);

// 等等於
users.stream()
.map(u -> findUserProfile(u.getId()))
.filter(Optional::isPresent)
.map(Optional::get)
.forEach(System.out::println);

尾巴:取值

ifPresent() - 有值時執行

1
2
3
4
5
6
7
8
9
10
11
12
Optional<User> user = Optional.ofNullable(getUser());

user.ifPresent(u -> {
System.out.println("User: " + u.getName());
updateUserActivity(u);
});

// 等等於
if (user.isPresent()) {
System.out.println("User: " + user.get().getName());
updateUserActivity(user.get());
}

ifPresentOrElse() - 有值/無值分別處理(Java 9+)

1
2
3
4
5
6
7
8
9
10
11
12
13
Optional<User> user = Optional.ofNullable(getUser());

user.ifPresentOrElse(
u -> System.out.println("Found: " + u.getName()),
() -> System.out.println("User not found")
);

// 等等於
if (user.isPresent()) {
System.out.println("Found: " + user.get().getName());
} else {
System.out.println("User not found");
}

orElse() - 無值時回傳預設值

1
2
3
4
Optional<User> user = Optional.ofNullable(getUser());

// 如果沒有值,回傳預設使用者
User result = user.orElse(createDefaultUser());

重要:orElse 的預設值會被直接求值

1
2
3
4
5
6
7
8
User result = user.orElse(expensiveUserCreation());
// 即使 user 有值,expensiveUserCreation() 也會被執行!

// 這在迴圈中特別糟糕
for (Long userId : userIds) {
User u = findUser(userId).orElse(createDefaultUser());
// 每次迴圈都會呼叫 createDefaultUser,即使找到了使用者
}

orElseGet() - 無值時才執行函式(Lazy)

1
2
3
4
5
6
7
8
9
10
Optional<User> user = Optional.ofNullable(getUser());

// 只有在沒有值時,才執行 lambda
User result = user.orElseGet(() -> createDefaultUser());

// 在迴圈中
for (Long userId : userIds) {
User u = findUser(userId).orElseGet(() -> createDefaultUser());
// createDefaultUser() 只在真正沒找到時才執行
}

orElse vs orElseGet 的差異表:

情境 orElse orElseGet
Optional 有值 預設值被求值 預設值不被求值(不執行)
Optional 無值 預設值被求值 預設值被求值(執行 supplier)
預設值耗時 浪費計算 只在需要時才計算

orElseThrow() - 無值時拋 exception

1
2
3
4
5
6
7
8
9
Optional<User> user = Optional.ofNullable(getUser(123));

// 如果沒找到,拋 NoSuchElementException
User u = user.orElseThrow();

// 或自訂 exception
User u = user.orElseThrow(() ->
new UserNotFoundException("User 123 not found")
);

實務範例

場景 1:API 回應

1
2
3
4
5
6
@GetMapping("/user/{id}")
public ResponseEntity<?> getUser(@PathVariable Long id) {
return userService.findUser(id)
.map(user -> ResponseEntity.ok(user))
.orElseGet(() -> ResponseEntity.notFound().build());
}

場景 2:DB 查詢 + 預設值

1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class UserService {

public User getUserOrDefault(Long id) {
return userRepository.findById(id)
.orElseGet(() -> {
User defaultUser = new User();
defaultUser.setName("Unknown");
return defaultUser;
});
}
}

場景 3:鏈接多個查詢

1
2
3
4
5
6
7
// 查詢優先級:DB → Cache → Default
public User getUser(Long id) {
return findUserInDB(id)
.or(() -> findUserInCache(id))
.or(() -> Optional.of(createDefaultUser()))
.get(); // 最終一定有值,安全 .get()
}

場景 4:過濾和轉換

1
2
3
4
5
6
7
8
9
List<Long> userIds = userRepository.findActiveUserIds();

List<String> activeNames = userIds.stream()
.map(id -> findUser(id)) // Optional<User>
.filter(Optional::isPresent) // 過濾有值的
.map(Optional::get) // 取出 User
.filter(u -> u.getLoginCount() > 0) // 再過濾
.map(User::getName) // 取名字
.collect(Collectors.toList());

常見踩坑

坑 1:orElse 的預設值總是被求值

1
2
3
4
5
6
7
// 錯誤 - 浪費 I/O
String config = loadConfig()
.orElse(fetchFromRemoteServer()); // 會被執行!

// 正確
String config = loadConfig()
.orElseGet(() -> fetchFromRemoteServer()); // lazy

坑 2:get() 可能拋 exception

1
2
3
4
5
6
7
8
9
10
11
12
// 危險
User user = Optional.ofNullable(getUser()).get(); // 可能拋 NoSuchElementException

// 安全
User user = Optional.ofNullable(getUser())
.orElseThrow(() -> new UserNotFoundException());

// 或
Optional<User> opt = Optional.ofNullable(getUser());
if (opt.isPresent()) {
User user = opt.get();
}

坑 3:map 陷阱 - 轉換函式回傳 null

1
2
3
4
5
6
7
Optional<User> user = Optional.of(new User("Alice"));

// 如果 getEmail() 回傳 null,map 的結果是 empty,不是 null
Optional<String> email = user.map(u -> u.getEmail());

// 所以這是安全的
String result = email.orElse("no-email"); // 不會是 null

坑 4:連鎖 filter 造成複雜代碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 複雜的鏈接
Optional<String> result = userRepository.findById(123)
.filter(u -> u.isActive())
.filter(u -> u.hasRole("ADMIN"))
.map(u -> u.getEmail())
.filter(email -> email.contains("@"))
.map(String::toUpperCase);

// 用 stream 會更清楚
Stream<String> results = userRepository.findById(123)
.stream()
.filter(u -> u.isActive() && u.hasRole("ADMIN"))
.map(User::getEmail)
.filter(email -> email.contains("@"))
.map(String::toUpperCase);

何時使用 Optional

該用 Optional:

  • 回傳值可能 null
  • API 方法簽名想明確表達「可能無值」
  • 函式式風格的串接操作

不該用 Optional:

  • 方法參數(用 @Nullable annotation 就好)
  • 集合:不用 Optional,用空集合
  • 必須有值的情況
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 不好
public void process(Optional<User> user) {
// 呼叫時 process(Optional.of(user)) 很彆扭
}

// 好
public void process(User user) {
// 或用 @Nullable
// public void process(@Nullable User user)
}

// 不好
public Optional<List<User>> findUsers() {
return Optional.ofNullable(query()); // 用空集合更好
}

// 好
public List<User> findUsers() {
List<User> results = query();
return results != null ? results : Collections.emptyList();
}

最後的話

Optional 一開始覺得拖泥帶水,用熟了才發現真的香。關鍵是記住三段式結構,懂得區分 map 和 flatMap,特別是 orElse 和 orElseGet 的差異。用好了,程式不但 null-safe,還超函式式。沒有 Optional 前的 Java,得自己檢查一堆 if (obj != null),現在直接用 .orElse(),乾淨多了。