Java 中的方法引用
1. 概述
Java 8 中最受欢迎的变化之一是引入了 lambda 表达式,因为它们允许我们放弃匿名类,大大减少了样板代码并提高了可读性。
方法引用是一种特殊类型的 lambda 表达式。它们通常用于通过引用现有方法来创建简单的 lambda 表达式。
方法引用有四种:
- 静态方法
- 特定对象的实例方法
- 特定类型的任意对象的实例方法
- 构造函数
在本教程中,我们将探索 Java 中的方法引用。
2. 静态方法的引用
我们将从一个非常简单的示例开始,大写并打印字符串列表:
List<String> messages = Arrays.asList("hello", "baeldung", "readers!");
我们可以通过利用简单的 lambda 表达式调用 StringUtils.capitalize()直接方法:
messages.forEach(word -> StringUtils.capitalize(word));
或者,我们可以使用方法引用来简单地引用capitalize静态方法:
messages.forEach(StringUtils::capitalize);
请注意,方法引用始终使用 :: 运算符。
3. 引用特定对象的实例方法
为了演示这种类型的方法引用,让我们考虑两个类:
public class Bicycle {
private String brand;
private Integer frameSize;
// standard constructor, getters and setters
}
public class BicycleComparator implements Comparator {
@Override
public int compare(Bicycle a, Bicycle b) {
return a.getFrameSize().compareTo(b.getFrameSize());
}
}
并且,让我们创建一个 BicycleComparator 对象来比较自行车车架尺寸:
BicycleComparator bikeFrameSizeComparator = new BicycleComparator();
我们可以使用 lambda 表达式按车架尺寸对自行车进行排序,但我们需要指定两辆自行车进行比较:
createBicyclesList().stream()
.sorted((a, b) -> bikeFrameSizeComparator.compare(a, b));
相反,我们可以使用方法引用让编译器为我们处理参数传递:
createBicyclesList().stream()
.sorted(bikeFrameSizeComparator::compare);
方法引用更加清晰且更具可读性,因为代码清楚地表明了我们的意图。
4. 引用特定类型的任意对象的实例方法
这种类型的方法引用与前面的示例类似,但无需创建自定义对象来执行比较。
让我们创建一个要排序的整数列表:
List<Integer> numbers = Arrays.asList(5, 3, 50, 24, 40, 2, 9, 18);
如果我们使用经典的 lambda 表达式,则两个参数都需要显式传递,而使用方法引用则更加简单:
numbers.stream()
.sorted((a, b) -> a.compareTo(b));
numbers.stream()
.sorted(Integer::compareTo);
尽管它仍然是一句台词,但方法参考更容易阅读和理解。
5. 构造函数的引用
我们可以像在第一个示例中引用静态方法一样引用构造函数。唯一的区别是我们将使用 new 关键字。
让我们从具有不同品牌的 String 列表中创建一个 Bicycle 数组:
List<String> bikeBrands = Arrays.asList("Giant", "Scott", "Trek", "GT");
首先,我们将向 Bicycle 类添加一个新的构造函数:
public Bicycle(String brand) {
this.brand = brand;
this.frameSize = 0;
}
接下来,我们将使用方法引用中的新构造函数,并从原始 String 列表中创建一个 Bicycle 数组:
bikeBrands.stream()
.map(Bicycle::new)
.toArray(Bicycle[]::new);
请注意我们如何使用方法引用调用 Bicycle 和 Array 构造函数,使我们的代码外观更加简洁和清晰。
6. 其他示例和限制
正如我们到目前为止所看到的,方法引用是使我们的代码和意图变得非常清晰和可读的好方法。但是,我们不能用它们来替换所有类型的 lambda 表达式,因为它们有一些限制。
它们的主要限制是其最大的优势:先前表达式的输出需要与引用的方法签名的输入参数相匹配。
让我们看一下此限制的示例:
createBicyclesList().forEach(b -> System.out.printf(
"Bike brand is '%s' and frame size is '%d'%n",
b.getBrand(),
b.getFrameSize()));
这个简单的例子无法用方法引用来表达,因为在我们的例子中,printf方法需要3个参数,而使用createBicyclesList().forEach()只会允许用于推断一个参数(Bicycle 对象)的方法引用。
最后,我们来探讨一下如何创建一个可以从 lambda 表达式引用的无操作函数。
在这种情况下,我们希望使用 lambda 表达式而不使用其参数。
首先,让我们创建 doNothingAtAll 方法:
private static <T> void doNothingAtAll(Object... o) {
}
由于它是一个 varargs 方法,因此它可以在任何 lambda 表达式中工作,无论引用的对象或推断的参数数量如何。
现在,让我们看看它的实际效果:
createBicyclesList()
.forEach((o) -> MethodReferenceExamples.doNothingAtAll(o));
七、结论
在这个快速教程中,我们了解了 Java 中的方法引用是什么以及如何使用它们来替换 lambda 表达式,从而提高可读性并阐明程序员的意图。
本文中提供的所有代码均可在 GitHub 上 获取。