CodeGym /课程 /JAVA 25 SELF /Method References (::): 方法引用

Method References (::): 方法引用

JAVA 25 SELF
第 21 级 , 课程 1
可用

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::toUpperCasemap 中是正确的,因为第一个参数就是 String 本身。但在期待静态方法的上下文中使用它会导致错误。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION