一開始看 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
// 不好的做法 - 即使 log level 是 INFO,也會執行運算
logger.debug("Current value is " + expensiveComputation());

// 好的做法 - 用 Supplier,只有在需要時才執行
logger.debug(() -> "Current value is " + expensiveComputation());

如果 log level 設定是 INFO(不印 DEBUG),右邊的 expensiveComputation() 永遠不會被執行。但左邊一定會執行,浪費 CPU。

Slf4j 的 logger 支援這種寫法:

1
2
3
4
5
// 實際上 Supplier 是你自己寫的 lambda
logger.debug("Processing started");
if (logger.isDebugEnabled()) {
logger.debug("Details: {}", () -> buildDetailedMessage()); // Supplier
}

實例 2:搭配 Optional

Optional 有個方法叫 orElseGet(Supplier)

1
2
3
4
5
6
User user = userRepository.findById(123)
.orElseGet(() -> createDefaultUser());

// vs orElse(T) - 會直接執行
User user = userRepository.findById(123)
.orElse(createDefaultUser()); // createDefaultUser() 會被直接呼叫

差異在於:

  • orElse(value) - value 一定會被求值,即使 Optional 有值
  • orElseGet(supplier) - 只有在 Optional 沒值時才執行 supplier

假設 createDefaultUser() 很耗時,用 orElseGet 可以省時間。

1
2
3
4
5
6
7
Optional<Config> config = loadConfigFromDB(123);

// 不好 - 即使 DB 有 config,也會建立預設 config
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()) {
// 只有在 needsValidation() 為 true 時,才執行 condition.get()
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(); // 只在這邊才呼叫 API
} 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());
}
}

// 使用 - 如果 cache 有值就直接回傳,沒有才執行 supplier
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
// 方法 1:直接傳值
public void logMessage(String message) {
if (isDebugEnabled()) {
System.out.println(message);
}
}

// 呼叫 - expensiveOperation() 一定會執行
logMessage("Result: " + expensiveOperation());

// 方法 2:用 Supplier
public void logMessage(Supplier<String> messageSupplier) {
if (isDebugEnabled()) {
System.out.println(messageSupplier.get()); // 只在需要時執行
}
}

// 呼叫 - expensiveOperation() 只在 isDebugEnabled() 為 true 時執行
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。