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引用,构造方法引用可用于创建对象实例。方法引用能显著简化代码,提高可读性,是函数式编程的重要特性。