方法引用通过方法的名字来指向一个方法。
方法引用可以使语言的构造更紧凑简洁,减少冗余代码。方法引用使用一对冒号 :: 。下面,我们在 Car 类中定义了 4 个方法作为例子来区分 Java 中 4 种不同方法的引用。方法引用实际上是某些 Lambda 表达式的更简洁写法,原因就是在这些情况下,编译器能够智能的推断出参数体中的值究竟是方法的传入参数还是调用者。
方法引用有以下四种形式:
类型 | 示例 |
构造方法引用 | ClassName::new |
静态方法引用 | ClassName::staticMethodName |
特定类的任意对象的方法引用 | ClassName::methodName |
特定对象的实例方法引用 | instance::methodName |
上面的四种形式的实例:
Supplier.java
package java.util.function;@FunctionalInterfacepublic interface Supplier{ T get();}
Car.java
package com.boomoom;import java.util.function.Supplier;public class Car { //Supplier是jdk1.8的接口,这里和lambda一起使用了 public static Car create(final Suppliersupplier) { return supplier.get(); } public static void collide(final Car car) { System.out.println("Collided " + car.toString() + " and it went boom!"); } public void follow(final Car another) { System.out.println("Following the " + another.toString()); } public void repair() { System.out.println("Repaired " + this.toString()); }}
Maintest.java
package com.boomoom;import java.util.Arrays;import java.util.List;public class MainTest { public static void main(String[] args) { // 构造器引用:它的语法是Class::new,或者更一般的Class::new实例如下: Car[] carList = new Car[2]; final Car car0 = Car.create(Car::new); carList[0] = car0; final Car car1 = Car.create(Car::new); carList[1] = car1; final List< Car > cars = Arrays.asList( carList ); // 静态方法引用:它的语法是Class::static_method,实例如下: cars.forEach( Car::collide ); // 特定类的任意对象的方法引用:它的语法是Class::method实例如下: cars.forEach( Car::repair ); // 特定对象的方法引用:它的语法是instance::method实例如下: final Car police = Car.create( Car::new ); cars.forEach( police::follow ); }}
构造方法引用
根据 Supplier 接口的定义,其为函数式接口,我们调用静态方法 create 创建 Car 对象的时候,代码应该是如下所示的:
1 Car.create(new Supplier() {2 @Override3 public Car get() {4 return new Car();5 }6 });
用 Lambda 表达式,更简单的写法就是:
1 Car.create(()->new Car());
可以看到,Car 类存在一个不带参数的构造方法,所以编译器不需要根据参数列表猜测构造方法的参数(因为都是空的),所以就有一个更加简单的写法:
1 Car.create(Car::new);
实际上,如果 Lambda 的参数个数和类的构造方法个数一致,也可以改写为上面的形式,只要是没有歧义即可。
静态方法引用
我们创建一个 Car 对象,接着将其添加进一个 List 中:
1 final Car car = Car.create(Car::new);2 final Listcars = Arrays.asList(car);
Java8 中给 Iterable 接口添加了 forEach 方法方便我们遍历集合类型。
现在假设我们要给 List 中的每个 Car 对象调用一次 Car.collide(Car car) 静态方法,那么可以使用 forEach 方法,而 forEach 方法需要传入一个 Consumer,恰好,这个 Consumer接口也带有 FunctionalInterface 注解,所以我们一步一步的来看:
1 cars.forEach(new Consumer() {2 @Override3 public void accept(Car car) {4 Car.collide(car);5 }6 });
写成 Lambda:
1 cars.forEach(c -> Car.collide(c));
就是对传进来的 Car 对象执行静态方法,很简单。但是实际上,对于静态方法,编译器也不需要推断调用者(类名),当传入参数和静态方法所需参数个数一致时,就不存在歧义:
所以这里可以直接使用方法引用:
1 cars.forEach(Car::collide);
类成员方法引用
类的成员方法不能是静态的,而这个情况其实和静态方法类似,区别是,Lambda 表达式的参数个数需要等于所调用方法的入参个数加一。
为什么要加一?
因为类的成员方法不能通过类名直接调用,只能通过对象来调用,也就是Lambda表达式的第一个参数,是方法的调用者,从第二个开始的参数个数要和需要调用方法的入参个数一致即可。如下图所示:
对于上面的例子,如果要对List中的每个对象执行一次它的repair方法:
1 cars.forEach(c -> c.repair());
根据上图,这里参数只有一个,而 repair 方法没有入参,所以不存在歧义,即可以改写为对应的方法引用:
1 cars.forEach(Car::repair);
对象方法引用
与类方法引用不同的是,对象方法引用方法的调用者是一个外部的对象。如下图:
对于上面例子,可以再创建一个 Car 的对象 police,并让 police 调用 follow 方法跟踪 List 中的每个 Car:
1 final Car police = Car.create(Car::new);2 cars.forEach((car1) -> police.follow(car1));
改成对象方法引用:
1 cars.forEach(police::follow);
至此,方法引用也完成了。
参考: