深入解析 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是一个测试框架,旨在简化广泛的测试需求,从单元测试(隔离测试一个类)到集成测试(测试由…...

数据预处理 matlab 数据质量评估
知乎 数据类型转换等 Mathworks 数据预处理 概念辨析 配对是同一批样本的前后比较,独立是两批不同样本的的比较 独立样本是指我们得到的样本是相互独立的。配对样本就是一个样本中的数据与另一个样本中的数据相对应的两个样本。配对样本可以消除由于样本指定的不公…...

对象存储, 开源MinIO docker-compose.yml 文件
文章目录 python SDK 文档地址:docker-compose.yml 文件控制台使用:应用服务中使用样例: python SDK 文档地址: https://min.io/docs/minio/linux/developers/python/API.html docker-compose.yml 文件 version: 3services:min…...

爬虫笔记(一):实战登录古诗文网站
需求:登录古诗文网站,账号+密码+图形验证码 第一:自己注册一个账号+密码哈 第二:图形验证码,需要一个打码平台(充钱,超能力power!)或…...

适用于 Windows 11 的 12 个最佳免费 PDF 编辑器
除了绘图等基本功能外,一些适用于 Windows 11 的免费 PDF 编辑器还具有 AI、OCR 识别和书签等高级功能。 我们的列表包含易于立即下载的 PDF 编辑软件工具。 这些工具不仅可以帮助转换 PDF、编辑、上传、删除、裁剪、分割、提取等。 PDF 是指便携式文档格式&…...

力扣每日一练(24-1-18)
经验一:不要把问题想复杂 Python: min_price float(inf)max_profit 0for price in prices:min_price min(min_price, price)max_profit max(max_profit, price - min_price)return max_profit C#: public int MaxProfit(int[] prices) {i…...

MyBatis 使用报错:org.xml.sax.SAXParseException 元素内容必须由格式正确的字符数据或标记组成
文章目录 前言问题分析解决方案方案一:使用 CDATA 区块,依然使用 “ > ” 或者 “ < ”方案二:使用转义字符 个人简介 前言 今天在使用 MyBatis 时出现报错: Caused by: org.xml.sax.SAXParseException: 元素内容必须由格式…...

PDF.js - 免费开源的 JavaScript 读取、显示 PDF 文档的工具库,由 Mozilla 开发并且持续维护
最近新项目需要处理 PDF,研究了 PDf.js 之后觉得很不错,于是写篇文章推荐给大家。 PDF.js 的功能和它的名字一样简单,是一个使用 HTML5 技术来让前端网页支持读取、解析和显示 PDF 文档的 JS 工具库。这个项目由大名鼎鼎的 Mozilla 组织开发…...

UI开发布局-HarmonyOS应用UI开发布局
UI页面的构建不用再像Android开发过程中在.xml文件中书写,可直接在页面上使用声明式UI的方式按照布局进行排列,构建应用的页面。 如下代码使用Row、Column构建一个页面布局,在页面布局中添加组件Text、Button,共同构成页面&#…...

大数据开发之Hadoop(完整版+练习)
第 1 章:Hadoop概述 1.1 Hadoop是什么 1、Hadoop是一个由Apache基金会所开发的分布式系统基础架构。 2、主要解决,海量数据的存储和海量数据的分析计算问题。 3、Hadoop通常是指一个更广泛的概念-Hadoop生态圈 1.2 Hadoop优势(4高…...

Redis与DB数据一致性-个人总结
缓存读写策略: Cache-Aside Pattern 读缓存: 先查询缓存,存在则返回, 如果不存在则查询DB, 再塞回缓存中,最后返回结果. 写缓存: 更新完成DB之后,删除缓存. 适合场景: 读比较多的场景,更新比较少的场景. 像我们工作当中&#…...