Java中Supplier延遲生成值的原因及分析
在編程中,延遲生成值(Lazy Value Generation) 是指將值的計(jì)算或生成過程推遲到真正需要使用該值時(shí)才執(zhí)行。
這一機(jī)制的核心是避免不必要的計(jì)算,提升程序的性能和資源利用率。結(jié)合 Supplier 和 Optional 的使用場景,我們可以從以下幾個(gè)方面理解延遲生成值:
一、核心概念:為什么需要延遲?
假設(shè)你需要一個(gè)默認(rèn)值,但這個(gè)默認(rèn)值的生成可能:
- 耗時(shí)(如數(shù)據(jù)庫查詢、網(wǎng)絡(luò)請求)。
- 耗資源(如創(chuàng)建大型對象、復(fù)雜計(jì)算)。
- 依賴狀態(tài)(如需要先判斷某個(gè)條件是否成立)。
如果提前生成默認(rèn)值,可能會(huì)導(dǎo)致:
- 性能浪費(fèi):即使最終不需要使用默認(rèn)值(比如
Optional中有值),也會(huì)白白執(zhí)行計(jì)算。 - 邏輯錯(cuò)誤:默認(rèn)值的生成可能依賴后續(xù)才會(huì)變化的狀態(tài)。
延遲生成的目標(biāo):僅在必要時(shí)執(zhí)行計(jì)算,減少無意義的開銷。
二、通過Supplier實(shí)現(xiàn)延遲生成
Supplier 是 Java 函數(shù)式接口(@FunctionalInterface),僅定義一個(gè)無參數(shù)方法 get(),用于返回一個(gè)值。
它的關(guān)鍵作用是:將值的生成邏輯封裝在 get() 中,而不立即執(zhí)行。
示例:模擬耗時(shí)操作
import java.util.Optional;
import java.util.UUID;
public class LazyDemo {
public static void main(String[] args) {
Optional<String> optional = Optional.empty(); // 假設(shè)無值
// 錯(cuò)誤寫法:提前執(zhí)行生成邏輯(無論是否需要)
String eagerValue = generateEagerly(); // 立即執(zhí)行
String value1 = optional.orElse(eagerValue);
// 正確寫法:延遲執(zhí)行生成邏輯(僅在必要時(shí))
String value2 = optional.orElseGet(() -> generateLazily()); // 僅當(dāng)optional為空時(shí)執(zhí)行
}
// 提前生成(立即執(zhí)行)
private static String generateEagerly() {
System.out.println("執(zhí)行提前生成邏輯");
return UUID.randomUUID().toString();
}
// 延遲生成(通過Supplier封裝)
private static String generateLazily() {
System.out.println("執(zhí)行延遲生成邏輯");
return UUID.randomUUID().toString();
}
}
執(zhí)行結(jié)果對比
當(dāng) optional 為空時(shí):
generateEagerly()會(huì)在調(diào)用orElse前就執(zhí)行,輸出執(zhí)行提前生成邏輯。generateLazily()僅在調(diào)用orElseGet且optional為空時(shí)執(zhí)行,輸出執(zhí)行延遲生成邏輯。
當(dāng) optional 有值時(shí)(如 Optional.of("有值")):
generateEagerly()仍然會(huì)執(zhí)行(浪費(fèi)計(jì)算)。generateLazily()不會(huì)執(zhí)行(節(jié)省資源)。
三、延遲生成的典型應(yīng)用場景
1.避免不必要的計(jì)算
// 假設(shè)getExpensiveData()是耗時(shí)操作 Optional<String> data = fetchData(); // 錯(cuò)誤:無論data是否有值,都會(huì)執(zhí)行g(shù)etExpensiveData() String result = data.orElse(getExpensiveData()); // 正確:僅當(dāng)data為空時(shí),才執(zhí)行g(shù)etExpensiveData() String result = data.orElseGet(() -> getExpensiveData());
2.依賴上下文的動(dòng)態(tài)默認(rèn)值
public class UserService {
private final Database database;
public User getUser(String userId) {
Optional<User> user = database.queryUser(userId);
// 僅當(dāng)用戶不存在時(shí),創(chuàng)建默認(rèn)用戶(依賴當(dāng)前時(shí)間)
return user.orElseGet(() -> createDefaultUser(LocalDateTime.now()));
}
private User createDefaultUser(LocalDateTime timestamp) {
// 需要當(dāng)前時(shí)間作為參數(shù),延遲生成時(shí)才能獲取最新值
return new User("default", timestamp);
}
}
- 如果提前生成默認(rèn)用戶(如
orElse(createDefaultUser(...))),可能使用過時(shí)的時(shí)間戳。 - 延遲生成時(shí),
createDefaultUser能獲取當(dāng)前最新的上下文數(shù)據(jù)(如實(shí)時(shí)時(shí)間)。
3.流式編程中的延遲計(jì)算
在 Java Stream 中,某些操作(如 filter、map)是惰性的(Lazy),僅在終端操作(如 forEach、collect)時(shí)才執(zhí)行。Supplier 可以配合這種特性優(yōu)化性能:
List<String> data = ...;
// 延遲生成隨機(jī)數(shù),僅在需要時(shí)(如過濾后的數(shù)據(jù)滿足條件)才生成
data.stream()
.filter(s -> s.startsWith("A"))
.findFirst()
.orElseGet(() -> UUID.randomUUID().toString()); // 僅當(dāng)無匹配元素時(shí)生成
四、延遲生成與立即生成的對比
| 場景 | 延遲生成(orElseGet) | 立即生成(orElse) |
|---|---|---|
| 性能 | 僅在必要時(shí)執(zhí)行計(jì)算,節(jié)省資源 | 無論是否需要,均提前執(zhí)行計(jì)算 |
| 適用場景 | 默認(rèn)值生成耗時(shí)、依賴動(dòng)態(tài)狀態(tài) | 默認(rèn)值已提前存在或生成成本極低 |
| 代碼可讀性 | 需通過 lambda 封裝邏輯,稍顯復(fù)雜 | 直接傳入值,代碼更簡潔 |
| 線程安全性 | 每次調(diào)用 get() 都會(huì)重新計(jì)算(需注意) | 提前生成一次,后續(xù)使用同一值 |
五、總結(jié):延遲生成的本質(zhì)
延遲生成值的核心是將計(jì)算邏輯與值的使用時(shí)機(jī)解耦,通過 Supplier 等工具將 “生成值” 的動(dòng)作推遲到真正需要的時(shí)刻。這一思想在 Java 中廣泛應(yīng)用于:
Optional的默認(rèn)值生成(orElseGet)。- 流式編程的惰性操作。
- 框架中的依賴注入(如 Spring 的
Lazy注解)。 - 緩存場景的按需加載(如
ConcurrentHashMap.computeIfAbsent)。
通過延遲生成,可以顯著提升程序的效率,避免 “過早計(jì)算” 帶來的性能損耗,尤其在處理高成本操作或動(dòng)態(tài)依賴時(shí)優(yōu)勢明顯。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java基礎(chǔ)之Unsafe內(nèi)存操作不安全類詳解
Java是面向?qū)ο笳Z言,在使用Java編程時(shí),大多數(shù)情況下都不會(huì)直接操作內(nèi)存,而且Java也不提倡直接操作內(nèi)存,但是Java中到底有沒有可以直接操作內(nèi)存的工具類呢?有!Java中提供Unsafe類可以用來來直接操作內(nèi)存,文中詳細(xì)介紹了Unsafe內(nèi)存操作不安全類,需要的朋友可以參考下2021-06-06
Java多線程中的ThreadLocal應(yīng)用場景及問題解讀
這篇文章主要介紹了Java多線程中的ThreadLocal應(yīng)用場景及問題解讀,ThreadLocal這個(gè)類在多線程并發(fā)中主要的使用場景是什么呢,我們都知道多線程并發(fā)問題實(shí)際就是多個(gè)線程對公共資源訪問和修改問題,需要的朋友可以參考下2023-12-12
SpringBoot算法實(shí)現(xiàn)數(shù)據(jù)加密傳輸
這篇文章主要為大家詳細(xì)介紹了SpringBoot算法實(shí)現(xiàn)數(shù)據(jù)加密傳輸?shù)南嚓P(guān)知識(shí),本文主要是混合加密,前端 SM2 + SM4,后端 Spring Boot + Hutool 解密,希望對大家有所幫助2026-03-03
Java自定義注解導(dǎo)入和導(dǎo)出合并一對多單元格工具類方式
文章主要介紹了excel工具類對poi的封裝,包括導(dǎo)入依賴、創(chuàng)建注解、IExcelUtils工具類代碼等內(nèi)容,示例通過訂單、訂單明細(xì)、追溯碼三類實(shí)體進(jìn)行導(dǎo)入和導(dǎo)出測試,展示了日期轉(zhuǎn)換、排序、隱藏字段等功能的應(yīng)用2026-04-04
Java String轉(zhuǎn)換時(shí)為null的解決方法
這篇文章主要介紹了Java String轉(zhuǎn)換時(shí)為null的解決方法,需要的朋友可以參考下2017-07-07

