Java 方法引用,用户希望使用方法引用来访问某个现有的方法,并将其作为 lambda 表达式进行处理,使用双冒号表示法(::
)将实例引用或类名与方法分开,如果说 lambda 表达式本质上是将方法作为对象进行处理,那么方法引用就是将现有方法作为 lambda 表达式进行处理。
Java 方法引用 问题描述
用户希望使用方法引用来访问某个现有的方法,并将其作为 lambda 表达式进行处理。
Java 方法引用 解决方案
使用双冒号表示法(::
)将实例引用或类名与方法分开。
Java 方法引用 具体实例
如果说 lambda 表达式本质上是将方法作为对象进行处理,那么方法引用就是将现有方法作为 lambda 表达式进行处理。
例如,java.lang.Iterable
接口的 forEach
方法传入 Consumer
作为参数。如例 1-8 所示,Consumer
可以作为 lambda 表达式或方法引用来实现。
例 1-8 利用方法引用访问
println
方法
Stream.of(3, 1, 4, 1, 5, 9)
.forEach(x -> System.out.println(x)); ➊
Stream.of(3, 1, 4, 1, 5, 9)
.forEach(System.out::println); ➋
Consumer<Integer> printer = System.out::println; ➌
Stream.of(3, 1, 4, 1, 5, 9)
.forEach(printer);
➊ 使用 lambda 表达式
➋ 使用方法引用
➌ 将方法引用赋给函数式接口
双冒号表示法在 System.out
实例上提供了对 println
方法的引用,它属于 PrintStream
类型的引用。方法引用的末尾无须括号。在本例中,程序将流的所有元素打印到标准输出。2
2讨论 lambda 表达式或方法引用时,很难不涉及流, Java 流的创建 将专门讨论流。目前可以这样理解:流依次产生一系列元素,但不会将它们存储在任何位置,也不会对原始源进行修改。
如果编写一个只有一行的 lambda 表达式来调用方法,不妨考虑改用等价的方法引用。
与 lambda 语法相比,方法引用具有几个(不那么显著的)优点。首先,方法引用往往更短。其次,方法引用通常包括含有该方法的类的名称。这两点使得代码更易于阅读。
如例 1-9 所示,方法引用也可以和静态方法一起使用。
例 1-9 在静态方法中使用方法引用
Stream.generate(Math::random) ➊
.limit(10)
.forEach(System.out::println); ➋
❶ 静态方法
❷ 实例方法
Stream
接口定义的 generate
方法传入 Supplier
作为参数。Supplier
是一个函数式接口,其单一抽象方法 get
不传入任何参数且只生成一个结果。Math
类的 random
方法与 get
方法的签名相互兼容,因为 random
方法同样不传入任何参数,且产生一个 0 到 1 之间、均匀分布的双精度伪随机数。方法引用 Math::random
表示该方法是 Supplier
接口的实现。
由于 Stream.generate
方法产生的是一个无限流(infinite stream),我们通过 limit
方法限定只生成 10 个值,然后使用方法引用 System.out::println
将这些值打印到标准输出,作为 Consumer
的实现。
语法
方法引用包括以下三种形式,其中一种存在一定的误导性。
object::instanceMethod
引用特定对象的实例方法,如 System.out::println
。
Class::staticMethod
引用静态方法,如 Math::max
。
Class::instanceMethod
调用特定类型的任意对象的实例方法,如 String::length
。
最后一种形式或许令人困惑,因为在 Java 开发中,一般只通过类名来调用静态方法。请记住,lambda 表达式和方法引用在任何情况下都不能脱离上下文存在。以对象引用为例,上下文提供了方法的参数。对于 System.out::println
,等效的 lambda 表达式为(如例 1-8 中的上下文所示):
// 相当于System.out::println
x -> System.out.println(x)
上下文提供了 x
的值,它被用作方法的参数。
静态方法 max
与之类似:
// 相当于Math::max
(x,y) -> Math.max(x,y)
此时,上下文需要提供两个参数,lambda 表达式返回较大的参数。
“通过类名来调用实例方法”语法的解释有所不同,其等效的 lambda 表达式为:
// 相当于String::length
x -> x.length()
此时,当上下文提供 x
的值时,它将用作方法的目标而非参数。
如果通过类名引用一个传入多个参数的方法,则上下文提供的第一个元素将作为方法的目标,其他元素作为方法的参数。
例 1-10 显示了相应的代码。
例 1-10 从类引用(class reference)调用多参数实例方法
List<String> strings =
Arrays.asList("this", "is", "a", "list", "of", "strings");
List<String> sorted = strings.stream()
.sorted((s1, s2) -> s1.compareTo(s2)) ➊
.collect(Collectors.toList());
List<String> sorted = strings.stream()
.sorted(String::compareTo) ➊
.collect(Collectors.toList());
➊ 方法引用及其等效的 lambda 表达式
Stream
接口定义的 sorted
方法传入 Comparator<T>
作为参数,其单一抽象方法为 int compare(String other)
。sorted
方法将每对字符串提供给比较器,并根据返回整数的符号对它们进行排序。在本例中,上下文是一对字符串。方法引用语法(采用类名 String
)调用第一个元素(lambda 表达式中的 s1
)的 compareTo
方法,并使用第二个元素 s2
作为该方法的参数。
在流处理中,如果需要处理一系列输入,则会频繁使用方法引用中的类名来访问实例方法。例 1-11 显示了对流中各个 String
调用 length
方法。
例 1-11 使用方法引用在
String
上调用length
方法
Stream.of("this", "is", "a", "stream", "of", "strings")
.map(String::length) ➊
.forEach(System.out::println); ➋
❶ 通过类名访问实例方法
❷ 通过对象引用访问实例方法
程序调用 length
方法将每个字符串转换为一个整数,然后打印所有结果。
方法引用本质上属于 lambda 表达式的一种简化语法。lambda 表达式在实际中更常见,因为每个方法引用都存在一个等效的 lambda 表达式,反之则不然。对于例 1-11 中的方法引用,其等效的 lambda 表达式如例 1-12 所示。
例 1-12 方法引用的等效 lambda 表达式
Stream.of("this", "is", "a", "stream", "of", "strings")
.map(s -> s.length())
.forEach(x -> System.out.println(x));
对任何 lambda 表达式来说,上下文都很重要。为避免歧义,不妨在方法引用的左侧使用 this
或 super
。