深入解析 Java 方法引用:Lambda 表达式的进化之路
前言
方法引用是 Java 8 提供的一种新特性,它允许我们更简洁地传递现有方法作为参数。这项特性实际上是对 Lambda 表达式的一种补充,通过方法引用,我们可以直接引用现有方法,而无需编写完整的Lambda表达式。最近在使用方法引用的过程中有了一些感悟,这里希望以文章的形式记录下来,与大家分享。

1. 背景
最近在使用 Mybatis-plus 这个框架,这个框架能在 Mybatis 的基础上减少简单 SQL 的编写,直接使用Java 代码的方式拼接我们想要的 SQL,我为了研究怎样才能不写 SQL 也是在尽量多地翻看 Mybatis-plus 相关文档,尽量能用代码解决地绝不写 SQL。

1.1. LambdaQueryMapper
LambdaQueryMapper 是 MyBatis-Plus 提供的一种基于 Lambda 表达式的查询方式。通过使用 LambdaQueryMapper,我们可以利用 Lambda 表达式来编写类型安全的、更具可读性的查询代码,而无需担心手动编写 SQL 或者处理字符串连接等操作。下面是一个查询示例:
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.additional.query.impl.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.service.additional.query.impl.LambdaQueryWrapper;public class UserService {private UserMapper userMapper;// 查询用户列表的方法示例public List<User> queryUserList(String username, Integer age, String email) {LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();// 构建查询条件queryWrapper.eq(User::getUsername, username).gt(age != null, User::getAge, age).like(email != null, User::getEmail, email);// 执行查询List<User> userList = userMapper.selectList(queryWrapper);return userList;}
}
在这个查询示例中,我们可以通过 User::getAge 指定 age 字段为查询条件,通过 User::getEmail来指定 email 为查询条件。这种形式的 Java 代码就叫做方法引用。
1.2. 函数式接口
说到方法引用,就不得不说函数式接口。在 java 中只有一个抽象方法的接口叫做函数式接口,每一种方法引用类型实际上对应一个函数式接口。
例如我们比较熟悉的 Runnable ,它其实就是一个函数式接口。它的定义如下:
@FunctionalInterface
public interface Runnable {public abstract void run();
}
它就代表可以接收一个没有参数也没有返回值的方法:
public class Test {public static void main(String[] args) {Test test = new Test();test.runnable(test::getXxx);}public void runnable(Runnable runnable){runnable.run();}public void getXxx(){System.out.println("run");}
}
同样的,如果我希望接收只有一个参数且只有一个返回值的方法进来,怎么做呢?Java 中也提供了这样的函数式接口 Function<T,R>:
@FunctionalInterface
public interface Function<T, R> {R apply(T t);
}
它接收一个参数 T,且返回一个类型 R,所以我们就可以这么用
public class Test {public static void main(String[] args) {Test test = new Test();test.function(test::getXyy);}public <T,R> void function(Function<T,R> function){}public Integer getXyy(String s){return 1;}
}
1.3. 对于 User::getAge 疑惑
通过上面的叙述,我们知道只要在方法中声明函数式接口,我们就可以使用方法引用的形式将函数作为方法参数传入方法中。但是我在查看 LambdaQueryWrapper 源码的过程中方法,它在 eq、like 等方法中定义的参数类型为函数是接口是 SFunction<T,R>
@FunctionalInterface
public interface SFunction<T, R> extends Function<T, R>, Serializable {
}
也就是说它本质也是一个 Function<T,R> ,匹配的是一个参数以及一个返回值的方法,但是 User::getAge对应的 get 方法只有返回值而没有参数,这是怎么匹配上的?

2. 理解
经过一番查找资料,我终于理解了。下面就由小的来解释一下其中的原由。方法引用我们可以将其分成两部分,以 User::getAge 为例,前半部分是 User,它既可以是 User 类,也可以是 User 对象。后半部分是 getAge 它既可以是类方法(静态方法)也可以是对象方法(非静态方法),理论上有四种组合,即:
- 对象-类方法
- 对象-对象方法
- 类-类方法
- 类-对象方法
2.1. 对象::类方法
这种形式是不被允许的,会直接报错:

2.2. 对象::对象方法
如果是这种,其实就是直接匹配,也就是说,方法的类型必须和方法引用对应,以 Function<T,R> 为例,必须得是一个参数和一个返回值,代码如下:
public class Test {public static void main(String[] args) {Test test = new Test();//引用为对象test.function(test::getXyy);}public <T,R> void function(Function<T,R> function){}public Integer getXyy(String s){return 1;}
}
2.3. 类::类方法
这种和上面是一样的,也是要遵循参数和返回值相对应的规则:
public class Test {public static void main(String[] args) {Test test = new Test();//引用变为类test.function(Test::getXyy);}public <T,R> void function(Function<T,R> function){}/*** 变为静态方法*/public static Integer getXyy(String s){return 1;}
}
2.4. 类::对象方法
而对于类::对象方法这种形式,它就有点特殊了,它存在一个隐式转换,我们先看代码:
public class Test {public static void main(String[] args) {Test test = new Test();test.function(Test::getXyy);}public <T,R> void function(Function<T,R> function){}/*** 无参数、一个返回值的对象方法*/public Integer getXyy(){return 1;}
}
function 期待的参数类型是:一个参数、一个返回值的方法,然而对应的却是:无参数、一个返回值的方法,这是怎么回事呢?
原来,对于这种类型的方法引用存在一个隐式转化,即:Test::getXyy 等价于 (Test t) -> t.getXyy(),而 (Test t) -> t.getXyy()代表的就是:一个参数,一个返回值的方法,所以才能匹配 Function<T,R>函数式接口类型。
为什么它需要做这样的转换?本质上是由于类是无法调用对象方法,所以对于 类::对象方法这种形式的方法引用都需要进行这样的隐式转换。
相关文章:
深入解析 Java 方法引用:Lambda 表达式的进化之路
前言 方法引用是 Java 8 提供的一种新特性,它允许我们更简洁地传递现有方法作为参数。这项特性实际上是对 Lambda 表达式的一种补充,通过方法引用,我们可以直接引用现有方法,而无需编写完整的Lambda表达式。最近在使用方法引用的…...
MySQL作业 (3)多表查询
多表查询 1.创建student和score表2.为student表和score表增加记录3.查询student表的所有记录4.查询student表的第2条到4条记录5.从student表查询所有学生的学号(id)、姓名(name)和院系(department)的信息6.…...
ConcurrentHashMap和HashMap的区别
什么是HashMap (1)HashMap 是基于 Map 接口的非同步实现,线程不安全,是为了快速存取而设计的;它采用 key-value 键值对的形式存放元素(并封装成 Node 对象),允许使用 null 键和 nul…...
MCM备赛笔记——图论模型
Key Concept 图论是数学的一个分支,专注于研究图的性质和图之间的关系。在图论中,图是由顶点(或节点)以及连接这些顶点的边(或弧)组成的。图论的模型广泛应用于计算机科学、通信网络、社会网络、生物信息学…...
算法笔记(动态规划入门题)
1.找零钱 int coinChange(int* coins, int coinsSize, int amount) {int dp[amount 1];memset(dp,-1,sizeof(dp));dp[0] 0;for (int i 1; i < amount; i)for (int j 0; j < coinsSize; j)if (coins[j] < i && dp[i - coins[j]] ! -1)if (dp[i] -1 || dp[…...
开发实践_阶段三
编写一个告知APP。 需求: 1.登录、注册 2.发布定向讯息:检测是否登录,是则向用户或用户组发布 ”名称 时间“ ;否则提示登录 3.讯息接收:检测是否登录,是则查看收到信息(未读数)…...
codegeex和通义灵码辅助编程——以及通义灵码无法登陆的bug解决
通义的速度更快,延迟低,150ms。 codegeex速度慢些,延迟较高,500ms。 个人评价:延迟低的会很好地改善使用体验,所以通义加分。 但是整体功能上还是codegeex强一些,可以选中代码进行对话…...
Android14之DefaultKeyedVector实现(一百八十二)
简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏:多媒…...
银河麒麟操作系统 v10 中离线安装 Docker
银河麒麟操作系统 v10 中离线安装 Docker 1. 查看系统版本2. 查看 Linux 内核版本(3.10以上)3. 查看 iptabls 版本(1.4以上)4. 判断处理器架构5. 离线下载 Docker 安装包6. 移动解压出来的二进制文件到 /usr/bin 目录中7. 配置 Do…...
如何系统的学习Python
学习 Python 的时候,可以按照以下步骤进行系统学习: 学习 Python 基础知识:首先了解 Python 的基础语法、数据类型、变量和运算符等基本概念。可以通过阅读《Python编程从入门到实践》等经典教材来建立基础。也可以通过翻阅Python官方文档来进…...
Java并发基础:一文讲清util.concurrent包的作用
java.util.concurrent包是 Java 中用于并发编程的重要工具集,提供了线程池、原子变量、并发集合、同步工具类、阻塞队列等一系列高级并发工具类,使用这些工具类可以极大地简化并发编程的难度,减少出错的可能性,提高程序的效率和可…...
C++PythonC# 三语言OpenCV从零开发(2):教程选择
文章目录 相关专栏前言视频教学和官方文档视频教程OpenCV 官方教程最终选择我的最终选择 相关专栏 C&Python&Csharp in OpenCV 前言 OpenCV 有官方的教程和简单的视频教程: OpenCV 官方教程 B站也有相关的视频教学 OpenCV4 C 快速入门视频30讲 - 系列合集 …...
【嘉立创EDA-PCB设计指南】3.网络表概念解读+板框绘制
前言:本文对网络表概念解读板框绘制(确定PCB板子轮廓) 网络表概念解读 在本专栏的上一篇文章【嘉立创EDA-PCB设计指南】2,将设计的原理图转为了PCB,在PCB界面下出现了所有的封装,以及所有的飞线属性&…...
nodejs前端项目的CI/CD实现(二)jenkins的容器化部署
一、背景 docker安装jenkins,可能你会反问,这太简单了,有什么好讲的。 我最近就接手了一个打包项目,它是一个nodejs的前端项目,jenkins已在容器里部署且运行OK。 但是,前端组很追求新技术,不…...
python爬虫案例分享
当然,我可以分享一个基本的Python爬虫示例。这个示例将使用Python的requests库来抓取网页内容,然后使用BeautifulSoup库来解析和提取信息。我们将构建一个简单的爬虫来从一个示例网站抓取标题。 Python爬虫示例 目标 提取某网站的标题。 需要的库 r…...
【CC++】为什么 scanf 函数在读取字符串时不需要用取地址运算符
在C语言中如何使用 scanf 读取字符串 在C语言中,字符串实际上是字符数组,所以我们可以使用scanf函数来读取字符串。但是,需要注意的是,scanf在读取字符串时会在遇到空格、制表符或换行符时停止。因此,它不能用于读取包…...
Linux dirs命令教程:dirs命令详解与实例(附实例详解和注意事项)
Linux dirs命令介绍 dirs这是一个内置在shell中的命令,用于显示当前被记忆的目录列表。默认状态下,它会按照stack的方式储存目录,即最后加入的目录会被首先列出来。 Linux dirs命令适用的Linux版本 dirs命令在所有常见的Linux发行版中都适…...
掌握虚拟化:PVE平台安装教程与技术解析
🌟🌌 欢迎来到知识与创意的殿堂 — 远见阁小民的世界!🚀 🌟🧭 在这里,我们一起探索技术的奥秘,一起在知识的海洋中遨游。 🌟🧭 在这里,每个错误都…...
Godot FileDialog无法访问其它盘符的文件
问题描述 使用Godot的FileDialog对象访问Windows系统的文件,例如: func _on_hud_sig_save():var dlg FileDialog.new()dlg.set_access(FileDialog.ACCESS_FILESYSTEM)dlg.set_file_mode(FileDialog.FILE_MODE_SAVE_FILE)add_child(dlg)dlg.popup_cent…...
TestNG注释
目录 TestNG注释列表 BeforeXXX和AfterXXX注释放在超类上时如何工作? 使用BeforeXXX和AfterXXX TestNG注释 TestNG是一个测试框架,旨在简化广泛的测试需求,从单元测试(隔离测试一个类)到集成测试(测试由…...
树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...
从WWDC看苹果产品发展的规律
WWDC 是苹果公司一年一度面向全球开发者的盛会,其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具,对过去十年 WWDC 主题演讲内容进行了系统化分析,形成了这份…...
DockerHub与私有镜像仓库在容器化中的应用与管理
哈喽,大家好,我是左手python! Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库,用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...
高频面试之3Zookeeper
高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个?3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制(过半机制࿰…...
【算法训练营Day07】字符串part1
文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接:344. 反转字符串 双指针法,两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...
NLP学习路线图(二十三):长短期记忆网络(LSTM)
在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...
关于 WASM:1. WASM 基础原理
一、WASM 简介 1.1 WebAssembly 是什么? WebAssembly(WASM) 是一种能在现代浏览器中高效运行的二进制指令格式,它不是传统的编程语言,而是一种 低级字节码格式,可由高级语言(如 C、C、Rust&am…...
IT供电系统绝缘监测及故障定位解决方案
随着新能源的快速发展,光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域,IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选,但在长期运行中,例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
Rapidio门铃消息FIFO溢出机制
关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系,以下是深入解析: 门铃FIFO溢出的本质 在RapidIO系统中,门铃消息FIFO是硬件控制器内部的缓冲区,用于临时存储接收到的门铃消息(Doorbell Message)。…...
