一開始看 Supplier 就覺得「幹嘛不直接傳值就好?」,後來用過幾次才明白,這東西在延遲執行的場景真的超香。
什麼是 Supplier
Supplier 是 Java 8 引入的函式介面(Functional Interface),定義很簡單:
1 2 3 4
| @FunctionalInterface public interface Supplier<T> { T get(); }
|
特點就是:
- 不接參數 - 呼叫時不需要傳任何東西
- 回傳一個值 - 回傳型別由泛型 T 決定
- 可以 lambda - 因為是 @FunctionalInterface,能用 lambda 表達
核心用途:延遲執行
Supplier 最強的地方就是「延遲執行」。把一段邏輯包成 Supplier,等到真正需要時才呼叫 .get() 去執行。
實例 1:Logger 的 Lazy Evaluation
最經典的例子:
1 2 3 4 5
| logger.debug("Current value is " + expensiveComputation());
logger.debug(() -> "Current value is " + expensiveComputation());
|
如果 log level 設定是 INFO(不印 DEBUG),右邊的 expensiveComputation() 永遠不會被執行。但左邊一定會執行,浪費 CPU。
Slf4j 的 logger 支援這種寫法:
1 2 3 4 5
| logger.debug("Processing started"); if (logger.isDebugEnabled()) { logger.debug("Details: {}", () -> buildDetailedMessage()); }
|
實例 2:搭配 Optional
Optional 有個方法叫 orElseGet(Supplier):
1 2 3 4 5 6
| User user = userRepository.findById(123) .orElseGet(() -> createDefaultUser());
User user = userRepository.findById(123) .orElse(createDefaultUser());
|
差異在於:
orElse(value) - value 一定會被求值,即使 Optional 有值
orElseGet(supplier) - 只有在 Optional 沒值時才執行 supplier
假設 createDefaultUser() 很耗時,用 orElseGet 可以省時間。
1 2 3 4 5 6 7
| Optional<Config> config = loadConfigFromDB(123);
Config result = config.orElse(buildDefaultConfig());
Config result = config.orElseGet(() -> buildDefaultConfig());
|
實例 3:條件判斷延遲求值
假設你有一個複雜的驗證邏輯,想要條件判斷 + lazy evaluation:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public boolean validate(Supplier<Boolean> condition) { if (needsValidation()) { return condition.get(); } return true; }
boolean result = validate(() -> { System.out.println("Running expensive validation..."); return checkDatabase() && checkExternalAPI(); });
|
實務範例集合
範例 1:API 失敗重試
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @Service public class ApiClient { public <T> T callWithRetry(Supplier<T> apiCall, int maxRetries) { for (int i = 0; i < maxRetries; i++) { try { return apiCall.get(); } catch (IOException e) { if (i == maxRetries - 1) throw e; Thread.sleep(1000 * (i + 1)); } } return null; } }
String result = apiClient.callWithRetry( () -> httpClient.get("https://api.example.com/data"), 3 );
|
範例 2:快取 + Supplier
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Service public class CacheService { private Map<String, Object> cache = new ConcurrentHashMap<>(); public <T> T getOrCompute(String key, Supplier<T> supplier) { return (T) cache.computeIfAbsent(key, k -> supplier.get()); } }
User user = cacheService.getOrCompute( "user_123", () -> userService.fetchFromDatabase(123) );
|
範例 3:條件執行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class FeatureToggle { public <T> T executeIfEnabled(String feature, Supplier<T> action) { if (isFeatureEnabled(feature)) { return action.get(); } return null; } }
Integer result = featureToggle.executeIfEnabled( "new_algorithm", () -> newAlgorithmService.process(data) );
|
Supplier vs 直接傳值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public void logMessage(String message) { if (isDebugEnabled()) { System.out.println(message); } }
logMessage("Result: " + expensiveOperation());
public void logMessage(Supplier<String> messageSupplier) { if (isDebugEnabled()) { System.out.println(messageSupplier.get()); } }
logMessage(() -> "Result: " + expensiveOperation());
|
跟其他函式介面比較
| 介面 |
參數 |
回傳 |
用途 |
| Supplier |
無 |
T |
產生值、延遲執行 |
| Consumer |
T |
無 |
消費值 |
| Function<T,R> |
T |
R |
轉換值 |
| Predicate |
T |
boolean |
判斷條件 |
效能提示
- 記憶體 - Supplier 物件本身很輕量,不會佔用太多空間
- 時間 - lambda 會被編譯成 invokedynamic,效能基本沒差
- GC - 每次 get() 都會建立新物件(如果有的話),但通常不是瓶頸
最後的話
Supplier 看起來簡單,但用對地方真的能大幅減少無謂的計算。特別是在 logger、cache、optional 這類地方,delayed evaluation 能省掉很多不必要的 CPU。下次寫程式時,想到「這個值可能不用算」,就該考慮用 Supplier。
你的鼓勵將被轉換為我明天繼續加班的動力(真的)。 ❤️