Supplier提供对象实例场景

wen java案例 1

本文目录导读:

Supplier提供对象实例场景

  1. 场景一:延迟加载 (Lazy Initialization)
  2. 场景二:创建复杂或可变的对象工厂
  3. 场景三:作为方法的参数或返回值,传递“构造逻辑”
  4. 场景四:测试与模拟 (Mocking & Test Data)
  5. 对比:Supplier vs. 直接在代码中 new

我们来详细探讨一下 Supplier 在 Java (以及类似函数式语言中) 中提供对象实例的几种典型场景。

核心观点:Supplier<T> 的核心价值在于将“对象的创建逻辑”封装并延迟到真正需要的时刻执行,而不是在代码声明时就创建。

它常用于解决以下问题:延迟加载、创建复杂对象、动态策略、以及生成测试数据。

下面我们分场景进行详细说明。


延迟加载 (Lazy Initialization)

这是最经典的场景,对象创建代价高(如数据库连接、网络资源、大型配置对象),且不一定在程序启动时就需要。Supplier 可以让你在真正调用 get() 时才去创建。

示例:创建数据库连接

import java.util.function.Supplier;
import java.sql.Connection;
import java.sql.DriverManager;
public class DatabaseService {
    // 方式一:传统方式(在类初始化时就创建,非常昂贵,且可能失败)
    // private static Connection connection = createExpensiveConnection();
    // 方式二:使用 Supplier 进行延迟加载
    private static Supplier<Connection> lazyConnectionSupplier = () -> {
        System.out.println("+++ 即将创建真实的数据库连接 +++");
        return createExpensiveConnection(); // 这个 lambda 在被 get() 调用时才执行
    };
    // 实际使用时,只会创建一次(需要额外处理单例)
    private static Connection connection = null;
    public static Connection getConnection() {
        if (connection == null) {
            connection = lazyConnectionSupplier.get(); // 首次调用才创建
        }
        return connection;
    }
    private static Connection createExpensiveConnection() {
        // 模拟一个耗时操作,比如创建数据库连接
        System.out.println("正在建立数据库连接...");
        // 实际代码中: return DriverManager.getConnection(url, user, password);
        return null; // 简化
    }
    public static void main(String[] args) {
        System.out.println("程序启动,但还没有调用连接");
        // 当业务真正需要时才触发创建
        Connection conn = getConnection();
        System.out.println("获取到连接:" + conn);
        Connection conn2 = getConnection(); // 这次返回的是同一个对象(懒加载单例)
        System.out.println("第二次获取:" + conn2);
    }
}

输出:

程序启动,但还没有调用连接
+++ 即将创建真实的数据库连接 +++
正在建立数据库连接...
获取到连接:null
第二次获取:null

优势: 避免了不必要的资源开销和初始化失败的风险,如果应用启动后,这个连接从未被使用,那么这个昂贵操作就永远不会执行。


创建复杂或可变的对象工厂

当一个对象的创建需要多个步骤、配置参数,或者对象创建逻辑会随着上下文改变时,Supplier 可以充当一个灵活的对象工厂。

示例:创建不同策略的报表生成器

import java.util.function.Supplier;
interface ReportGenerator {
    void generate();
}
class PDFReportGenerator implements ReportGenerator {
    public void generate() {
        System.out.println("生成 PDF 报表");
    }
}
class CSVReportGenerator implements ReportGenerator {
    public void generate() {
        System.out.println("生成 CSV 报表");
    }
}
public class ReportService {
    // Supplier 作为工厂的抽象
    public void generateReport(Supplier<ReportGenerator> generatorSupplier) {
        System.out.println("准备报表环境...");
        ReportGenerator generator = generatorSupplier.get(); // 由调用方决定创建哪种报表
        generator.generate();
        System.out.println("报表生成完成。");
    }
    public static void main(String[] args) {
        ReportService service = new ReportService();
        // 根据用户选择,传入不同的供应商
        String reportType = "PDF"; // 假设从配置或用户输入获取
        Supplier<ReportGenerator> supplier;
        if ("PDF".equals(reportType)) {
            supplier = PDFReportGenerator::new; // 方法引用,等同于 () -> new PDFReportGenerator()
        } else {
            supplier = CSVReportGenerator::new;
        }
        service.generateReport(supplier);
    }
}

优势: 将对象的创建(供应)与使用(消费)完全解耦。ReportService 不需要知道具体实现类,只需接收一个能提供对象的 Supplier


作为方法的参数或返回值,传递“构造逻辑”

当你想在方法签名中表达“我需要一个由你负责创建的对象,但创建时机由我决定”时,Supplier 是最佳选择。

示例:通用的异常重试机制

import java.util.function.Supplier;
public class RetryUtil {
    // 接收一个 Supplier,尝试 3 次获取对象,成功则返回,否则抛出异常
    public static <T> T retryOnFailure(Supplier<T> supplier, int maxRetries) {
        for (int i = 0; i < maxRetries; i++) {
            try {
                return supplier.get(); // 尝试创建或调用核心逻辑
            } catch (Exception e) {
                System.out.println("第 " + (i + 1) + " 次尝试失败: " + e.getMessage());
                if (i == maxRetries - 1) {
                    throw new RuntimeException("所有重试都已失败", e);
                }
                // 可以在这里添加一些等待逻辑
            }
        }
        return null; // 不会执行到这里
    }
    public static void main(String[] args) {
        // 调用重试机制,传入一个可能会失败的复杂对象的创建逻辑
        String result = retryOnFailure(() -> {
            // 假设这是一个可能因为网络问题而失败的 API 调用
            return callExternalAPI();
        }, 3);
        System.out.println("最终结果: " + result);
    }
    private static String callExternalAPI() {
        // 模拟:第一次失败,第二次成功
        if (Math.random() > 0.5) {
            System.out.println("API 调用成功");
            return "Success Data";
        } else {
            throw new RuntimeException("Network Error");
        }
    }
}

优势: retryOnFailure 方法只关心如何重试,不关心具体创建什么对象或执行什么逻辑,调用方通过 Supplier 注入具体的行为。


测试与模拟 (Mocking & Test Data)

在单元测试中,Supplier 非常有用,可以轻松地创建一个对象生成器,用于快速生成测试数据。

示例:生成测试用的用户对象

import java.util.UUID;
import java.util.function.Supplier;
import java.util.stream.Stream;
class User {
    private String id;
    private String name;
    public User(String id, String name) {
        this.id = id;
        this.name = name;
    }
    @Override
    public String toString() {
        return "User{" + "id='" + id + '\'' + ", name='" + name + '\'' + '}';
    }
}
public class TestDataFactory {
    public static void main(String[] args) {
        // 创建一个 Supplier 来生成默认的用户(懒加载)
        Supplier<User> defaultUserSupplier = () -> new User("default-id", "Default User");
        // 创建一个更有趣的 Supplier:每次生成一个随机的用户
        Supplier<User> randomUserSupplier = () -> new User(
                UUID.randomUUID().toString().substring(0, 8),
                "User_" + (int)(Math.random() * 1000)
        );
        // 在测试中,你可以使用它来生成数据流
        System.out.println("默认用户(只创建一次):" + defaultUserSupplier.get());
        System.out.println("\n随机用户列表:");
        Stream.generate(randomUserSupplier) // 无限生成
                .limit(5)
                .forEach(System.out::println);
    }
}

优势: 快速、灵活地生成测试数据,特别是当对象的构造函数复杂或需要重复创建时,结合 Stream.generate() 可以轻松生成无限的测试数据流。


对比:Supplier vs. 直接在代码中 new

特性 使用 Supplier 直接 new
控制时机 延迟到 .get() 调用时 立即执行
传递逻辑 可将创建逻辑作为参数传递 无法传递,除非使用工厂模式
解耦程度 高,调用者不关心如何创建 低,调用者需要知道具体类
代码表现力 更函数式,声明“我需要一个这个” 更命令式,声明“我现在就要这个”
适用场景 复杂工厂、延迟加载、重试、测试数据 简单/直接的对象创建

Supplier 的核心场景是当你需要将“如何得到某个实例”作为一个可传递、可延迟执行的数据或被调用的逻辑时,它比直接调用构造方法提供了更高层次的抽象和灵活性,是函数式编程思想在对象创建领域的完美体现。

  • 延迟加载:减少启动/初始化开销。
  • 对象工厂:灵活创建不同策略的实例。
  • 方法参数:作为回调,将对象的创建逻辑注入到另一个方法中。
  • 测试数据:快速生成测试对象流。

抱歉,评论功能暂时关闭!