一、方法引用的功能
lambda表达式有时会涉及一个方法:
var timer = new Timer(100,event->System.out.println(event));
只要出现一个定时器事件就打印这个事件对象,这时可以直接把println方法传递到Timer构造器:
var timer = new Timer(100, System.out::println);
表达式System.out::println就是一个方法引用,它指示编译器生成一个函数式接口的实例,覆盖这个接口的抽象方法来调用给定的方法。
方法引用也不是一个对象,但是为一个类型为函数式接口的变量赋值时会生成一个对象。
二、方法引用的形式
1. object::instanceMethod
这种形式下,方法引用等价于向方法传递参数的lambda表达式,参考下面的示例,依然沿用Interval类:
定义一个CompareInterval类,分别实现一个static方法,一个instance方法:
class CompareInterval {
public static int compareStatic(Interval o1, Interval o2) {
return functionalInterface.compareFunc(o1.getStart(), o2.getStart());
}
public int compareInstance(Interval o1, Interval o2) {
return functionalInterface.compareFunc(o1.getStart(), o2.getStart());
}
}
如拟使用compareInstance作为两个Interval的比较方法,可先创建一个CompareInterval的实例,通过方法引用传递给sort:
/**Object::instanceMethod */
CompareInterval cmpi = new CompareInterval();
Collections.sort(list, cmpi::compareInstance);
2. Class::instanceMethod
例如:
String::concat
和前一种不同,这个表达式等同于(x,y)->x.concat(y),第一个参数x会成为方法的隐式参数,也就是说,传递的方法为concat(String),但编译器生成的函数式接口为FunctionalInterface(xxx,String),第一个参数的类型由上下文决定。
再如:
Collections.sort(list, CompareInterval::compareInstance);
像上一个例子中那样传递compareInstance方法,编译器会报错,因为这个表达式会对应一个参数列表为(xxx,Interval,Interval)的函数式接口,这不是sort方法想要的。
3. Class::staticMethod
第三种形式是传递类的静态方法,如要传递compareStatic方法:
Collections.sort(list, CompareInterval::compareStatic);
这个表达式等价于(x,y)->CompareInterval.compareStatic(x,y),即所有参数都将传递到这个静态方法。
API也会包含一些专门用作方法引用的方法,例如,Objects类的isNull方法,用于测试一个对象引用是否为null,Objects.isNull(obj)等价于obj==null,这时方法引用的优势就体现出来了,它可以传递到任何由Predicate参数的方法,如要从一个列表周删除所有null引用,就可以调用:
list.removeIf(Objects::isNull);
这样做要比直接使用lambda表达式e->e==null有更好的可读性。
三、小结
本文简要讨论了利用方法引用实现函数式接口的方法及方法引用的三种形式。
方法引用与lambda表达式存在一些相同点:
(1)两者都不能独立存在,总是会转换为函数式接口的实例;
(2)两者都不是对象,但是赋值操作会生成一个函数式接口类型的对象。