Java学习之旅第三季-18:方法引用

18.1 方法引用概述

除了 lambda 表达式之外,Java 8 还引入了另一个新的特性,其通过语言语法的改变实现了这一变化,即通过方法引用(Method Reference)来创建 Lambda 表达式。这是一种简化的语法糖,利用新的 "::"(双冒号)运算符来引用现有的方法,而非从现有方法创建一个 Lambda 表达式,从而简化了代码。

语法形式如下:

复制代码
<限定>::<方法名>

该方法不会立即运行,会在目标类型调用时运行。根据期望替换的 Lambda 表达式以及需要引用的方法类型,可以使用以下几种具体引用方式:

  • TypeName::staticMethod:引用类、接口或枚举中的一个静态方法
  • objectRef::instanceMethod:引用对象的实例方法
  • ClassName::instanceMethod:引用类的实例方法
  • TypeName.super::instanceMethod:引用父类的实例方法
  • ClassName::new:引用类的构造方法
  • ArrayTypeName::new:引用数组的"构造方法"

本小节使用的案例如下:

java 复制代码
public class Reference {
    public Reference() {
    }

    public Reference(String name) {
        System.out.println(name);
    }
    
    /**
     * 计算两个整数之和
     *
     * @param num1 第一个整数
     * @param num2 第二个整数
     * @return 两个整数之和
     */
    public static int add(int num1, int num2) {
        return num1 + num2;
    }
    /**
     * 计算两个整数之差
     *
     * @param num1 第一个整数
     * @param num2 第二个整数
     * @return 两个整数之差
     */
    public int sub(int num1, int num2) {
        return num1 - num2;
    }
}

18.2 静态方法引用

静态方法引用的形式为:

复制代码
TypeName::staticMethod   // 前面是某个类型,后面是静态方法名

现在先声明一个函数式接口:

java 复制代码
@FunctionalInterface
public interface MyInterface1 {
    int process(int num1, int num2);
}

该函数式接口可以使用Lambda表达式赋值,例如:

java 复制代码
MyInterface1 obj = (num1, num2) -> num1 + num2;

如果希望使用已经存在的方法,就可以使用方法引用的语法:

java 复制代码
MyInterface1 obj = Reference::add;
System.out.println(obj.process(10, 20));

由于方法引用是在Reference的静态方法add上,使用 Reference::add 就能正确的赋值到 MyInterface1类型的变量。

MyInterface1如果如果泛型接口也是可以的:

java 复制代码
public interface MyInterface1<T extends Number> {
    T process(T num1, T num2);
}

将add的方法引用赋值给MyInterface1完全没问题,将两个实参传给函数式接口的方法即可调用到Reference的add方法:

java 复制代码
MyInterface1<Integer> obj1 = Reference::add;
System.out.println(obj1.process(10, 20));      // 输出 30

18.3 对象实例方法引用

要在对象的实例方法上应用方法引用,则需要使用以下的语法的形式:

复制代码
objectRef::instanceMethod    // 前面是某个对象,后面是实例方法名

这一次我们将Reference对象上声明的实例方法 sub 使用方法引用赋值给函数式接口MyInterface1:

java 复制代码
Reference reference = new Reference();        // 声明 Reference 类型的对象 reference
MyInterface1<Integer> onj2 = reference::sub;
System.out.println(onj2.process(10, 20));     // 输出 -10

如果是在非静态代码块或非静态方法中,其当前类中有合适的方法,也可以使用this来引用本类中的方法,这一次方法的参数及返回值类型都是 double,所以泛型函数式接口的类型参数就是 Double:

java 复制代码
public class ReferenceDemo {
    private double sub(double num1, double num2) {
        return num1 - num2;
    }
    
 	{
        MyInterface1<Double> obj2 = this::sub;
        System.out.println(obj2.process(1.2, 3.4));
    }
    
    public void test() {
        MyInterface1<Double> obj2 = this::sub;
        System.out.println(obj2.process(1.2, 3.4));
    }
}

也可以引用父类的方法,比如,有这样一个函数式接口:

复制代码
public interface MyInterface3 {
    String method();
}

在ReferenceDemo中增加一个重写的 toString 方法与一个非静态方法 getInfo 如下:

java 复制代码
public void getInfo() {
    MyInterface3 obj = this::toString;
    System.out.println(obj.method());
    obj = super::toString;  // 引用父类的toString方法
    // 下面这句与上面这句效果是一样的
    obj = ReferenceDemo.super::toString;
    System.out.println(obj.method());
}

@Override
public String toString() {
    return "ReferenceDemo toString()";
}

在主方法中调用 getInfo() 方法,运行结果如下:

复制代码
ReferenceDemo toString()
com.laotan.article18.ReferenceDemo@5674cd4d

可以看到分别调用了本类的 toString() 与父类(Object)的toString().

18.4 类实例方法引用

类的实例方法引用语法形式如下:

复制代码
ClassName::instanceMethod   // 前面是类名,后面是实例方法名

这种类型的方法引用可能会与静态方法引用混淆。不过其中的ClassName 表示被引用的实例方法所在的实例类型。它也是 Lambda 表达式的第一个参数。这样引用方法就会在传入的实例上被调用。请看下面的该函数式接口:

java 复制代码
@FunctionalInterface
public interface MyInterface2 {
    int process(Reference reference, int num1, int num2);
}

其中的方法第1个参数就是Reference类型,意味着调用时实参是Reference类型的实例,如果使用方法引用与Lambda表达式如下所示:

java 复制代码
MyInterface2 obj = Reference::sub;                      // 引用类的实例方法
obj = ((r, num1, num2) -> reference.sub(num1, num2));   // 对应的 Lambda 表达式

这种引用语法并非绑定到特定的对象上。相反,它们指向的是某个类型的一个实例方法,从函数式接口中抽象方法的声明与Lambda表达式可以看到,第一个参数就是类的实例,所以在调用时需要传入的第一个实参必须是该类的实例:

java 复制代码
Reference reference = new Reference();
System.out.println(obj.process(reference, 10, 20));   // 输出 -10

当然也可以改进函数式接口为泛型版本:

java 复制代码
@FunctionalInterface
public interface MyInterface2<T,R> {
    R process(T t, R num1, R num2);
}

这样能引用的方法就不局限于某一个特定类的静态方法了:

java 复制代码
MyInterface2<Reference, Integer> obj2 = Reference::sub;

18.5 构造方法引用

最后一种方法引用指的是一个类型的构造方法。构造方法方法引用的格式如下:

复制代码
ClassName::new      // 前面是类名,后面是关键字new

构造方法与方法类似,有参数列表,且会返回一个实例,下面声明一个泛型版本的函数式接口如下:

java 复制代码
public interface MyInterface4<T, R> {
    R method(T t);
}

上面函数式接口的唯一方法只接收了一个参数,且有返回结果;当然也可能没有参数,且不需要返回值:

java 复制代码
public interface MyInterface5 {
    void method();
}

现在我们就可以使用上面两个函数式接口接收构造方法引用或Lambda表达式:

java 复制代码
MyInterface4<String,Reference> o1 = Reference::new;   // Reference有String参数的构造方法
o2 = s -> new Reference(s);                           // 对应的 Lambda 表达式

MyInterface5 o2 = Reference::new;         // Reference 的无参构造方法
o2 = () -> {                              // 对应的 Lambda 表达式,没有接收构造方法的返回值
    new Reference();
};

针对数组也有类似的用法:

java 复制代码
MyInterface4<Integer, int[]> array = int[]::new;  // 引用数组的"构造方法"
array = size -> new int[size];                    // 对应的Lambda表达式
int[] nums = array.method(5);                     // 创建大小为5 的int 数组

18.6 小结

Java 8 引入的方法引用是一种简化Lambda表达式的语法糖,通过"::"运算符引用现有方法。主要包括静态方法引用、对象实例方法引用、类实例方法引用和构造方法引用。方法引用不会立即执行,而是在目标类型调用时运行。静态方法引用可直接通过类名调用,实例方法引用需要通过对象或this引用,构造方法引用可用于创建对象实例。方法引用能显著简化代码,提高可读性,是函数式编程的重要特性。

相关推荐
lang201509284 小时前
Spring依赖注入与配置全解析
java·spring
百锦再4 小时前
破茧成蝶:全方位解析Java学习难点与征服之路
java·python·学习·struts·kafka·maven·intellij-idea
羊锦磊4 小时前
[ Redis ] SpringBoot集成使用Redis(补充)
java·数据库·spring boot·redis·spring·缓存·json
兮动人5 小时前
Maven 多配置文件的使用
java·maven·maven 多配置文件的使用
毕设源码-钟学长5 小时前
【开题答辩全过程】以 餐健一体化管理系统为例,包含答辩的问题和答案
java·eclipse
摇滚侠5 小时前
Spring Boot3零基础教程,整合 SSM,笔记52
java·spring boot·笔记
毕设源码-朱学姐6 小时前
【开题答辩全过程】以 查寝打卡系统为例,包含答辩的问题和答案
java·eclipse
QMY5205207 小时前
爬虫的意义
java·spring·tomcat·maven
lang201509287 小时前
Spring Boot Actuator深度解析与实战
java·spring boot·后端