写在最前:本文全程参考《Java核心技术卷I》,添加了一些个人的思考和整理。如有错误欢迎指出!
目录
方法引用
1、方法引用的引入
假设你希望只要出现一个定时器时间,就打印这个对象,可以这么写:
var timer = new Timer(1000, event -> System.out.println(event));
但是,如果直接把println
方法传递到Timer构造器就更好了。具体做法如下:
var timer = new Timer(1000, System.out::println);
表达式System.out::println
是一个方法引用
它指示编译器生成一个函数式接口的实例,覆盖这个接口的抽象方法来调用给定的方法。
方法引用通过方法的名字来指向一个方法。
方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
2、方法引用的用法
2.1 解释说明
在var timer = new Timer(1000, System.out::println);
中,System.out::println
会用于构建一个ActionListener对象,它的actionPerformed(ActionEvent e)
方法会调用System.out.println(e)
对于重载方法的选择,编译器会根据上下文确定要使用哪一个方法,上面对于ActionListener对象,编译器选择了pirntln(Object)
方法,而对于Runnable task = System.out::println;
,Runnable函数式接口有一个无参数的抽象方法void run()
,编译器会选择无参数的println()
方法
类似于lambda表达式,方法引用也不是一个对象。但是,为一个类型为函数式接口的变量赋值时会生成一个对象。运行时,方法引用最终都会转换为函数式接口的实例。
如果你相对字符串排序,而不考虑字母的大小写,可以传递以下表达式:
Arrays.sort(strings, String::compareToIgnoreCase);
2.2 方法引用的形式
-
object::instanceMethod
方法引用等价于想方法传递参数的lambda表达式。
对于System.out::println
而言,对象是System.out
,方法表达式等价于x -> System.out.println(x)
-
Class::instanceMethod
第一个参数会成为方法的隐式参数。
如String::compareToIgnoreCase
等同于(x,y) -> x.compareToIgnoreCase(y)
-
Class::staticMethod
所有参数都传递到静态方法中:
Math::pow
等价于(x,y) -> Math.pow(x,y)
-
Class<T>::new
传递一个构造器引用。具体可看本文第5节
2.3 使用示例
方法引用 | 等价的lambda | 说明 |
---|---|---|
separator::equals | x -> separator.equals(x) | 对象+实例方法 |
String::trim | x -> x.trim() | 类+实例方法 |
Integer::concat | (x, y) -> x.concat(y) | 类+实例方法 |
Integer::valueOf | (x, y) -> Integer::valueOf(x) | 类+静态方法 |
Integer::sum | (x, y) -> Integer::sum(x, y) | 类+静态方法 |
Integer::new | x -> new Integer(x) | 构造器引用 |
Integer[]::new | n -> new Integer[n] | 数组构造器引用 |
2.4 其他特点
方法引用可以使用this
和super
关键字。this::equals
等同于x -> this.equals(x)
,super::method
可以传递父类的方法引用。
3、方法引用的使用示例
参考:菜鸟教程
4、方法引用和lambda的选择
只有当lambda表达式的体值调用一个方法而不做其他操作时,才能把lambda表达式重写为方法引用。
例如:s -> s.length() == 0
包含了一个方法调用以及比较,所以不能使用方法引用。
5、构造方法引用
5.1 简介
如Person::new
就是Person的构造器引用。具体引用哪个构造器取决于上下文
5.2 泛型数组与数组构造函数引用
java无法构造泛型类型的数组,表达式new T[n]
会出现错误,因为在编译时会因为泛型擦除而改为new Object[n]。使用方法引用就可以间接根据泛型构造数组
原因和做法可以参考我的另一篇博客:泛型篇笔记(二) 泛型底层原理——泛型擦除
现在,假设我们需要一个Person对象数组,Stream接口有一个toArray
方法可以返回Object数组:Object[] people = stream.toArray();
。不过,这样得到的结果仍是Object[]
。而通过构造器引用就可以解决这个问题:
Person[] people = stream.toArray(Person::new);
toArray
方法将通过这个构造器来创建一个正确类型的数组,然后填充并返回这个数组