编写Lambda表达式作为方法引用
您看到,lambda表达式实际上是方法的实现:函数式接口的唯一抽象方法。有时人们称这些lambda表达式为“匿名方法”,因为它只是:一个没有名称的方法,你可以在应用程序中传递,存储在一个字段或变量中,作为参数传递给一个方法或构造函数,并从一个方法返回。
有时你会编写lambda表达式,只是调用在其他地方定义的特定方法。事实上,当你写下面的代码时,你已经做到了:
Consumer<String> printer = s -> System.out.println(s);
这样写,这个lambda表达式只是在System.out上定义的println()方法的引用
这就是方法引用语法的作用所在。有时,lambda表达式只是对现有方法的引用。在这种情况下,你可以把它写成一个方法引用。然后前面的代码变成了下面的代码:
Consumer<String> printer = System.out::println;
方法引用有四种类型:
- 静态方法引用
- 绑定方法引用
- 未绑定的方法引用
- 构造函数方法引用
printer consumer 属于未绑定方法引用类别。
大多数情况下,IDE都能够告诉您某个特定的lambda表达式是否可以写成lambda表达式。不要犹豫,尽管问吧!
编写静态方法引用
假设你有以下代码:
DoubleUnaryOperator sqrt = a -> Math.sqrt(a);
这个lambda表达式实际上是对静态方法Math.sqrt()的引用。可以这样写:
DoubleUnaryOperator sqrt = Math::sqrt;
这个特定的方法引用引用了一个静态方法,因此称为静态方法引用。静态方法引用的一般语法是RefType::staticMethod。
静态方法引用可以接受多个参数。看以下代码:
IntBinaryOperator max = (a, b) -> Integer.max(a, b);
你可以用方法引用重写它:
IntBinaryOperator max = Integer::max;
编写未绑定的方法引用
不接受任何参数的方法
假设你有以下代码:
Function<String, Integer> toLength = s -> s.length();
这个function (可以写成ToIntFunction)只是对String类的length()方法的引用。所以你可以把它写成方法引用:
Function<String, Integer> toLength = String::length;
这种语法一开始可能会让人感到困惑,因为它实际上看起来像一个静态调用。但实际上并非如此:length()方法是String类的一个实例方法。
您可以使用这样的方法引用从普通Java bean调用任何getter。假设用户类上定义了getName()。然后可以编写以下函数:
Function<User, String> getName = user -> user.getName();
作为以下方法的参考:
Function<String, Integer> toLength = User::getName;
不接受任何参数的方法
这是你已经看到的另一个例子:
BiFunction<String, String, Integer> indexOf = (sentence, word) -> sentence.indexOf(word);
这个lambda实际上是对String类的indexOf()方法的引用,因此可以写成以下方法引用:
BiFunction<String, String, Integer> indexOf = String::indexOf;
这个语法看起来可能比简单的String::length或User::getName更容易混淆。在脑海中重构以经典方式编写的lambda的一个好方法是检查这个方法引用的类型。这会给你这个lambda 的参数。
非绑定方法引用的一般语法如下:RefType:instanceMethod,其中RefType是类型的名称,instanceMethod是实例方法的名称。
编写绑定方法引用
你看到的第一个方法引用的例子如下:
Consumer<String> printer = System.out::println;
此方法引用称为绑定方法引用。这个方法引用被称为绑定的,因为调用方法的对象是在方法引用本身中定义的。所以这个调用被绑定到方法引用中给出的对象。
如果考虑未绑定语法:Person::getName,可以看到调用方法的对象不是该语法的一部分:它是作为lambda表达式的参数提供的。看以下代码:
Function<User, String> getName = User::getName;
User anna = new User("Anna");
String name = getName.apply(anna);
您可以看到,该函数应用于User的一个特定实例,该实例被传递给该函数。然后该函数对该实例进行操作。
这不是前面的consumer 示例中的情况:在System.out对象上调用println()方法,这是方法引用的一部分。
绑定方法引用的一般语法如下:expr:instanceMethod,其中expr是返回对象的表达式,而instanceMethod是实例方法的名称。
编写构造函数方法引用
您需要知道的最后一种方法引用是构造函数方法引用。假设你有以下Supplier<List>:
Supplier<List<String>> newListOfStrings = () -> new ArrayList<>();
你可以像其他的一样看到这一点:这可以归结为ArrayList的空构造函数的引用。方法引用可以做到这一点。但由于构造函数不是方法,这是另一类方法引用。语法如下:
Supplier<List<String>> newListOfStrings = ArrayList::new;
您可以注意到这里不需要菱形运算符。如果你想把它,那么你还需要提供类型:
Supplier<List<String>> newListOfStrings = ArrayList<String>::new;
您需要意识到这样一个事实:如果不知道方法引用的类型,那么您就不能确切地知道它做什么。下面是一个例子:
Supplier<List<String>> newListOfStrings = () -> new ArrayList<>();
Function<Integer, List<String>> newListOfNStrings = size -> new ArrayList<>(size);
变量newListOfStrings和newListOfNStrings都可以用相同的语法ArrayList::new编写,但它们引用的不是同一个构造函数。你需要小心点。
包装方法引用
下面是四种类型的方法引用。
名称 | 语法 | lambda表达式 |
---|---|---|
Static | RefType::staticMethod | (args) -> RefType.staticMethod(args) |
Bound | expr::instanceMethod | (args) -> expr.instanceMethod(args) |
Unbound | RefType::instanceMethod | (arg0, rest) -> arg0.instanceMethod(rest) |
Constructor | ClassName::new | (args) -> new ClassName(args) |