深入理解Java函数式编程:Supplier与延迟创建对象实战
目录
一.Supplier接口的核心概念与语法基础
1. 什么是Supplier?
2. 基础热身:感受延迟创建的威力
二.揭秘双冒号(::)构造器引用
1. 为什么可以这么写?
2. Lambda与双冒号的等价转换
三.延迟创建对象在实际场景中的巨大好处
1. 模拟重量级资源的按需加载
2. 带来的实际收益
四.避坑指南:警惕频繁触发的副作用
在Java 8引入Lambda表达式和函数式接口后,我们的代码编写方式发生了巨大的改变。在日常开发中,我们可能会遇到诸如
Supplier<Customer> customerSupplier = Customer::new;这样看似“奇特”的写法。这其实涉及到了Java中的延迟初始化(Lazy Initialization)思想。本文将带你通过实战代码,彻底搞懂这一套机制。
一.Supplier接口的核心概念与语法基础
1. 什么是Supplier?
在
java.util.function包下,Supplier<T>是一个标准的函数式接口。它的核心特征是:无参数输入,有返回值输出。你可以把它理解为一个“工厂”或者一个“待命的动作”,它封装了创建某个对象的逻辑,但不会立即执行。
2. 基础热身:感受延迟创建的威力
为了直观感受传统写法和Supplier的区别,我们先看一段对比代码
import java.util.function.Supplier; class Customer { private String name; public Customer() { this.name = "默认客户"; System.out.println("[Customer] 构造方法被调用,对象已创建!"); } } public class Main { public static void main(String[] args) { // 1. 传统写法:立刻创建对象 System.out.println("--- 传统写法 ---"); Customer c1 = new Customer(); // 2. Supplier 写法:只是把“创建动作”打包起来,此时并没有执行 System.out.println("--- Supplier 写法 ---"); Supplier<Customer> customerSupplier = Customer::new; // 3. 只有真正调用 get() 的时候,才会去创建对象 System.out.println("--- 调用get()方法,才真正创建Customer对象 ---"); Customer c2 = customerSupplier.get(); } }运行结果:
--- 传统写法 --- [Customer] 构造方法被调用,对象已创建! --- Supplier 写法 --- --- 调用get()方法,才真正创建Customer对象 --- [Customer] 构造方法被调用,对象已创建!观察点:运行这段代码你会发现,打印的顺序完美印证了“延迟创建”。当你定义
customerSupplier时,控制台没有任何输出;直到调用了get(),对象才真正诞生。
二.揭秘双冒号(::)构造器引用
在上述代码中,最让人疑惑的莫过于Customer::new这种平时不常见的写法。这其实是Java 8引入的构造器引用(Constructor Reference)。
1. 为什么可以这么写?
因为Supplier<Customer>内部只有一个抽象方法T get(),它不需要任何参数并返回一个Customer对象。而Customer类的无参构造方法public Customer()刚好符合这个签名。因此,JVM 允许我们用类名::new来直接指向这个构造方法。
2. Lambda与双冒号的等价转换
如果你觉得Customer::new难以理解,它在底层完全等价于以下Lambda表达式:
Supplier<Customer> customerSupplier = () -> new Customer();当构造方法非常简单且明确时,使用::new不仅能让代码更加简洁优雅,还能提升代码的可读性。
三.延迟创建对象在实际场景中的巨大好处
既然可以直接new,为什么还要大费周章地使用Supplier进行延迟创建呢?其核心优势在于性能优化与资源节约。
1. 模拟重量级资源的按需加载
假设我们有一个非常耗时的数据库连接类:
class HeavyDatabaseConnection { public HeavyDatabaseConnection() { try { System.out.println(" 正在连接数据库... (假装耗时3秒)"); Thread.sleep(3000); System.out.println(" 数据库连接成功!"); } catch (InterruptedException e) { e.printStackTrace(); } } }如果我们把这个连接的创建过程交给Supplier处理:
public static void processOrder(boolean needDb, Supplier<HeavyDatabaseConnection> dbSupplier) { if (needDb) { HeavyDatabaseConnection db = dbSupplier.get(); // 需要时才连库 } else { System.out.println("本次订单不需要查库,直接跳过!"); } }2. 带来的实际收益
- 避免无效开销:如果业务分支判断不需要数据库,那么那3秒钟的连接耗时就被完美省下了。
- 解耦对象创建:你传递给方法的不是笨重的实体对象,而是轻量的“获取规则”。这使得方法的设计更加灵活。
四.避坑指南:警惕频繁触发的副作用
虽然Supplier很好用,但在练习时必须注意它的一个特性:每次调用
get()都会重新执行内部的逻辑。如果你在循环中不断调用
supplier.get(),它就会不断地为你创建新对象。这就意味着它天生不是单例模式。如果在多线程或高频调用场景下,既要实现延迟加载,又要保证只创建一次,就需要结合双重检查锁定(Double-Checked Locking)等并发手段来进行进阶优化了。
以上就是本篇文章的全部内容,喜欢的话可以留个免费的关注呦~~~
