【Java】梳理方法引用的来龙去脉

95 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情

在掌握lambda表达式的使用后, 就不得不谈谈方法引用, 他和lambda表达式有什么关系?

还记得我们在上一节讲到的lambda表达式的常用场景: lambda表达式作为一个实现了接口抽象方法的子类对象, 作为参数传入方法中, 相当于给方法传入了一个函数, 这是函数式编程思想的实践.

你可能不记得具体细节了, 我们来回顾一下

有个接口:

interface IPrintStr{
    void clockin(int kilometre);
}

用lambda表达式来实现接口, 并且调用:

public class Demo{
    // 作用是调用接口的方法.
    private static void exerciseReminder(IPrintStr data){
        data.clockin(2);
    }

    public static void main(String[] args) {
        // lambda表达式的本质为匿名函数,并重写了其中的抽象方法
        // lambda表达式定义了在exerciseReminder中调用的方法的具体实现
        exerciseReminder(x-> System.out.println("今天打卡任务:"+x+"公里"));
        exerciseReminder(x-> System.out.println(x+"公里已完成"));
    }
}

这么看好像lambda表达式也没多简洁? 如果你还有两个其他方法: task和finishtask

public static void task(int kilometer){
    System.out.println("今天打卡任务: "+kilometer+"公里!");
}
public static void finishtask(int kilometer){
    System.out.println(kilometer+"公里已完成!");
}

通过方法引用, 你可以简化代码:

public static void main(String[] args) {
    exerciseReminder(Demo::task);
    exerciseReminder(Demo::finishtask);
}

这里的方法引用, 形式两个冒号, 后面跟已有的方法名(如task, finishtask), 冒号前是该方法所定义的位置, 如果定义在Demo类中, 就用Demo::task表示方法引用.

具体解析为什么能这样做:

这里相当于在exerciseReminder中传入一个子接口, 子接口实现了父接口的抽象方法:

// 传入的子接口用匿名内部类对象表示相当于:
new IPrintStr{
    void clockin (int kilometre){
        // 如果是task方法的引用, 就是
        System.out.println("今天打卡任务: "+kilometer+"公里!");

        // 如果是finishtask方法引用, 就相当于
        System.out.println(kilometer+"公里已完成!");
    }
}

方法引用exerciseReminder(Demo::task);

这里exerciseReminder要接收一个IPrintStr接口对象, 然后在exerciseReminder中调用IPrintStr接口的clockin方法, 但问题是, IPrintStr是个接口, 他只定义了clockin方法, 但没写具体实现, 所以exerciseReminder必须接收一个实现了该方法的IPrintStr的具体子类.

由于该IPrintStr接口的功能单一,我们既可以先创建一个子实现对象,然后传入exerciseReminder方法,也可以利用lambda表达式简化代码编写,更直接的,既然关键点在于该接口的抽象方法需要具体实现, 那么我们可以直接传入一个方法当做该抽象方法的具体实现, 这就叫做方法引用!