1. 引言
Method reference(方法引用)是 Java 中的一种特殊语法,允许把已有的方法(或构造器)作为函数式接口的实现进行传递。只要签名匹配,在期待 lambda 表达式的地方就可以“传递”方法。
语法:
Class::method
object::method
Class::new
如果说 lambda 是“就地写一个小函数”,那么 method reference 就是“直接传已有的方法”。这就像与其抄写一份菜谱,不如给出那一页的链接。
直观示例
与其这样:
list.forEach(s -> System.out.println(s));
可以这样:
list.forEach(System.out::println);
是不是更简洁?
2. 方法引用的类型
方法引用有四种基本形式。这些就足够了。
引用静态方法
语法: Class::staticMethod
示例:
Function<Integer, String> intToString = String::valueOf;
System.out.println(intToString.apply(123)); // "123"
使用 lambda 的等价写法:
Function<Integer, String> intToString = i -> String.valueOf(i);
引用对象的实例方法
语法: object::method
示例:
PrintStream printer = System.out;
Consumer<String> consumer = printer::println;
consumer.accept("你好,世界!");
等价的 lambda:
Consumer<String> consumer = s -> printer.println(s);
引用类的实例方法
语法: Class::method
在这种形式下,函数式接口的第一个参数会作为调用该方法的目标对象。
示例:
Function<String, Integer> stringLength = String::length;
System.out.println(stringLength.apply("Java")); // 4
这里 String::length 等价于函数: (String s) -> s.length()
引用构造器
语法: Class::new
示例:
Supplier<ArrayList<String>> listSupplier = ArrayList::new;
ArrayList<String> list = listSupplier.get();
等价的 lambda:
Supplier<ArrayList<String>> listSupplier = () -> new ArrayList<>();
3. 何时使用方法引用?
当 lambda 只是调用已有方法且不包含额外逻辑时,使用方法引用更合适——代码更短也更易读。
示例:排序列表
与其这样:
List<String> names = Arrays.asList("Ivan", "Peter", "Anna");
names.sort((a, b) -> a.compareToIgnoreCase(b));
可以这样:
names.sort(String::compareToIgnoreCase);
示例:处理集合
与其这样:
list.forEach(s -> System.out.println(s));
可以更简洁:
list.forEach(System.out::println);
示例:元素转换
与其这样:
List<String> numbers = Arrays.asList("1", "2", "3");
List<Integer> ints = numbers.stream()
.map(s -> Integer.parseInt(s))
.collect(Collectors.toList());
可以这样:
List<Integer> ints = numbers.stream()
.map(Integer::parseInt)
.collect(Collectors.toList());
关于 Stream API 和实用类 Collectors 的更多内容,你会在第 30 级学到 :P
4. 方法引用与 lambda 表达式对比
等价性
方法引用与 lambda 表达式常可互换。只要签名匹配,两者都可用来实现函数式接口。
示例:
Consumer<String> c1 = s -> System.out.println(s);
Consumer<String> c2 = System.out::println;
何时更适合使用方法引用?
- 当 lambda 只是调用现有方法且没有额外逻辑。
- 为了提升可读性,尤其是在长调用链中。
- 当你希望明确表达:“这里只是调用一个方法”。
何时不适合使用方法引用?
- 如果需要附加逻辑(校验、条件、错误处理)。
- 如果在调用方法前需要先转换参数。
示例:
list.forEach(s -> {
if (s != null) System.out.println(s);
});
// 这里不适合使用方法引用,只能用 lambda。
5. 实战:用方法引用重写 lambda 表达式
示例 1:输出动物名称
List<String> animals = Arrays.asList("猫", "狗", "鹦鹉");
animals.forEach(animal -> System.out.println(animal));
重写为:
animals.forEach(System.out::println);
示例 2:把字符串转换为数字
List<String> numbers = Arrays.asList("10", "20", "30");
List<Integer> ints = numbers.stream()
.map(s -> Integer.parseInt(s))
.collect(Collectors.toList());
重写为:
List<Integer> ints = numbers.stream()
.map(Integer::parseInt)
.collect(Collectors.toList());
示例 3:按名称对对象排序
List<Animal> animalList = ...;
animalList.sort((a, b) -> a.getName().compareTo(b.getName()));
重写为:
animalList.sort(Comparator.comparing(Animal::getName));
这里的 Animal::getName 是对类的实例方法的引用。
示例 4:通过构造器创建对象
Supplier<Dog> dogFactory = () -> new Dog();
Dog dog = dogFactory.get();
重写为:
Supplier<Dog> dogFactory = Dog::new;
Dog dog = dogFactory.get();
6. 签名匹配如何运作
只有当方法签名与函数式接口的抽象方法一致时,才能使用方法引用。
@FunctionalInterface
interface IntToString {
String convert(int value);
}
public class Demo {
public static String intToHex(int value) {
return Integer.toHexString(value);
}
public static void main(String[] args) {
IntToString converter = Demo::intToHex;
System.out.println(converter.convert(255)); // ff
}
}
这里 Demo::intToHex 之所以可用,是因为它接收 int 并返回 String。
7. 带参数构造器与方法引用
即使构造器带有参数,只要签名匹配,也可以使用方法引用。
@FunctionalInterface
interface AnimalFactory {
Animal create(String name);
}
class Animal {
private String name;
public Animal(String name) { this.name = name; }
public String getName() { return name; }
}
AnimalFactory factory = Animal::new;
Animal cat = factory.create("Barsik");
System.out.println(cat.getName()); // Barsik
8. 使用方法引用的常见错误
错误 1:签名不匹配。
如果方法签名与接口的抽象方法不一致,编译器会报错。比如,接口期望两个参数,而引用指向的是只接收一个参数的方法。
错误 2:在没有对象的情况下使用类的实例方法引用。
当使用 Class::method 这种形式时,接口的第一个参数会成为调用该方法的对象。如果把参数的数量或顺序弄错,就会导致匹配错误。
错误 3:在需要额外逻辑的场景使用方法引用。
如果需要条件、日志或异常处理——请使用 lambda,而不是方法引用。
错误 4:引用重载方法。
如果类中存在多个同名方法,编译器可能无法确定选哪个。有时需要显式声明函数式接口的类型来消歧。
错误 5:在没有合适上下文时使用实例方法引用。
例如,String::toUpperCase 在 map 中是正确的,因为第一个参数就是 String 本身。但在期待静态方法的上下文中使用它会导致错误。
GO TO FULL VERSION