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");
Optional<User> opt = Optional.of(user);
Optional<User> opt2 = Optional.of(null);
|
ofNullable() - 可能 null
1 2 3 4 5 6
| User user = getUserFromDB(123);
Optional<User> opt = Optional.ofNullable(user);
|
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());
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());
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());
|
flatMap() - 值本身是 Optional
當轉換函式本身回傳 Optional 時,用 flatMap:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public Optional<Address> getAddress(User user) { return Optional.ofNullable(user.getAddress()); }
Optional<User> user = Optional.ofNullable(getUser());
Optional<Optional<Address>> address1 = user .map(u -> getAddress(u));
Optional<Address> address = user .flatMap(u -> getAddress(u));
|
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);
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())) .flatMap(Optional::stream) .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());
for (Long userId : userIds) { User u = findUser(userId).orElse(createDefaultUser()); }
|
orElseGet() - 無值時才執行函式(Lazy)
1 2 3 4 5 6 7 8 9 10
| Optional<User> user = Optional.ofNullable(getUser());
User result = user.orElseGet(() -> createDefaultUser());
for (Long userId : userIds) { User u = findUser(userId).orElseGet(() -> 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));
User u = user.orElseThrow();
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
| public User getUser(Long id) { return findUserInDB(id) .or(() -> findUserInCache(id)) .or(() -> Optional.of(createDefaultUser())) .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)) .filter(Optional::isPresent) .map(Optional::get) .filter(u -> u.getLoginCount() > 0) .map(User::getName) .collect(Collectors.toList());
|
常見踩坑
坑 1:orElse 的預設值總是被求值
1 2 3 4 5 6 7
| String config = loadConfig() .orElse(fetchFromRemoteServer());
String config = loadConfig() .orElseGet(() -> fetchFromRemoteServer());
|
坑 2:get() 可能拋 exception
1 2 3 4 5 6 7 8 9 10 11 12
| User user = Optional.ofNullable(getUser()).get();
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"));
Optional<String> email = user.map(u -> u.getEmail());
String result = email.orElse("no-email");
|
坑 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<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) { }
public void process(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(),乾淨多了。
你的鼓勵將被轉換為我明天繼續加班的動力(真的)。 ❤️