深入解析 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 或者处理字符串连接等操作。下面是一个查询示例:

java 复制代码
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 ,它其实就是一个函数式接口。它的定义如下:

java 复制代码
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

它就代表可以接收一个没有参数也没有返回值的方法:

java 复制代码
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>:

java 复制代码
@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

它接收一个参数 T,且返回一个类型 R,所以我们就可以这么用

java 复制代码
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>

java 复制代码
@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> 为例,必须得是一个参数和一个返回值,代码如下:

java 复制代码
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. 类::类方法

这种和上面是一样的,也是要遵循参数和返回值相对应的规则:

java 复制代码
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. 类::对象方法

而对于类::对象方法这种形式,它就有点特殊了,它存在一个隐式转换,我们先看代码:

java 复制代码
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>函数式接口类型。

为什么它需要做这样的转换?本质上是由于类是无法调用对象方法,所以对于 类::对象方法这种形式的方法引用都需要进行这样的隐式转换。

相关推荐
L.EscaRC10 分钟前
Lua语言知识与应用解析
java·python·lua
S7777777S16 分钟前
easyExcel单元格动态合并示例
java·excel
间彧19 分钟前
什么是Region多副本容灾
后端
爱敲代码的北20 分钟前
WPF容器控件布局与应用学习笔记
后端
爱敲代码的北20 分钟前
XAML语法与静态资源应用
后端
清空mega22 分钟前
从零开始搭建 flask 博客实验(5)
后端·python·flask
爱敲代码的北26 分钟前
UniformGrid 均匀网格布局学习笔记
后端
刘个Java26 分钟前
对接大疆上云api---实现直播效果
java
用户95451568116228 分钟前
== 和 equals 区别及使用方法组件封装方法
java
hashiqimiya32 分钟前
html的input的required
java·前端·html