前言
方法引用是 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; } }
在这个查询示例中,我们可以通过
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> ,匹配的是一个参数以及一个返回值的方法,但是
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 期待的参数类型是:一个参数、一个返回值的方法,然而对应的却是:无参数、一个返回值的方法,这是怎么回事呢?
原来,对于这种类型的方法引用存在一个隐式转化,即:
为什么它需要做这样的转换?本质上是由于类是无法调用对象方法,所以对于