Java 方法引用

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 表达式来说,上下文都很重要。为避免歧义,不妨在方法引用的左侧使用 thissuper

Python教程

Java教程

Web教程

数据库教程

图形图像教程

大数据教程

开发工具教程

计算机教程