news 2026/6/1 17:15:02

深入理解Java函数式编程:Supplier与延迟创建对象实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深入理解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)等并发手段来进行进阶优化了。

以上就是本篇文章的全部内容,喜欢的话可以留个免费的关注呦~~~

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/1 17:09:22

简单聊聊RAG:我花了三天才搞懂它是个啥?

我最近被身边的朋友们问傻了&#xff1a;“AI怎么老答非所问&#xff1f;”“不是说大模型很牛吗&#xff0c;怎么连我上个月去哪旅游都记不住&#xff1f;”面对这些灵魂拷问&#xff0c;我只能耸耸肩——其实AI就像个记忆力不好的学霸&#xff0c;背了一堆知识但不会翻书。直…

作者头像 李华
网站建设 2026/6/1 17:09:17

暗网高危攻击框架,剑指关键基础设施

近日&#xff0c;国际安全研究团队Lab52在暗网深处展开溯源调查时&#xff0c;发现一款正被大规模推广的专业化攻击框架。与常规网络攻击工具不同&#xff0c;该框架的攻击目标并非普通个人设备&#xff0c;而是直接锁定能源、电力等关乎国计民生的关键基础设施控制系统&#x…

作者头像 李华
网站建设 2026/6/1 17:07:20

如何彻底解决微信QQ消息撤回问题:Windows防撤回工具终极指南

如何彻底解决微信QQ消息撤回问题&#xff1a;Windows防撤回工具终极指南 【免费下载链接】RevokeMsgPatcher :trollface: A hex editor for WeChat/QQ/TIM - PC版微信/QQ/TIM防撤回补丁&#xff08;我已经看到了&#xff0c;撤回也没用了&#xff09; 项目地址: https://gitc…

作者头像 李华
网站建设 2026/6/1 17:05:17

Arduino贪吃蛇实战:从状态机到I2C LCD的嵌入式开发全解析

1. 项目概述与核心思路用一块小小的Arduino Uno开发板和一块16x2的LCD显示屏&#xff0c;就能复刻出我们童年记忆里的贪吃蛇游戏&#xff0c;这听起来是不是有点酷&#xff1f;作为一个玩了十几年嵌入式开发的老鸟&#xff0c;我始终觉得&#xff0c;把复杂的系统逻辑塞进资源极…

作者头像 李华