← 返回题目列表

方法引用是什么?有哪四种形式?和 Lambda 有什么关系?

中等 第 20 / 24 题 更新于 2026/07/26
方法引用Lambda函数式接口构造器引用

简化版

方法引用(Method Reference)是 Lambda 的进一步简化——当一个 Lambda 只是「调用一个已有的方法」时,可以用 :: 语法直接「引用」那个方法,省去写 Lambda 的样板。比如 s -> System.out.println(s) 可以简写成 System.out::println。它有四种形式① 静态方法引用 类名::静态方法(如 Integer::parseInt);② 特定对象的实例方法引用 对象::实例方法(如 System.out::println);③ 特定类型任意对象的实例方法引用 类名::实例方法(如 String::toLowerCase);④ 构造器引用 类名::new(如 ArrayList::new)。方法引用和 Lambda 完全等价,只是「这个 Lambda 只是转发调用一个方法」时更简洁。

详细版

四种方法引用形式

形式语法等价的 Lambda例子
① 静态方法引用类名::静态方法x -> 类名.静态方法(x)Integer::parseInt
② 特定对象的实例方法对象::实例方法x -> 对象.实例方法(x)System.out::println
③ 特定类型任意对象的实例方法类名::实例方法(a, b) -> a.实例方法(b)String::compareTo
④ 构造器引用类名::newx -> new 类名(x)ArrayList::new
List<String> names = List.of("Tom", "jerry", "Bob");

// ① 静态方法引用
names.stream().map(Integer::parseInt);           // s -> Integer.parseInt(s)

// ② 特定对象的实例方法引用(System.out 是特定对象)
names.forEach(System.out::println);              // s -> System.out.println(s)

// ③ 特定类型任意对象的实例方法引用(每个 String 调自己的方法)
names.stream().map(String::toUpperCase);         // s -> s.toUpperCase()
names.sort(String::compareTo);                   // (a, b) -> a.compareTo(b)

// ④ 构造器引用
names.stream().map(StringBuilder::new);          // s -> new StringBuilder(s)
Supplier<List<String>> listMaker = ArrayList::new;  // () -> new ArrayList<>()

第 ③ 种最容易混——String::toUpperCase 看起来像调静态方法,其实是「每个流元素调用自己的 toUpperCase」:

String::toUpperCase 等价于 s -> s.toUpperCase()
  → 方法引用里没写具体对象,那个对象就是"传进来的参数自己"
  → 所以叫"特定类型(String)的任意对象"的实例方法引用

⚠️ 第 ② 种(对象::方法)和第 ③ 种(类名::方法)最容易搞混,区别在「方法的接收者(那个对象)是谁」:② 是「一个确定的对象」(System.out),调用时对象固定;③ 是「参数本身」(String::toUpperCase 里每个元素调自己的方法),调用时对象是传进来的参数。判断口诀::: 左边是「具体对象」就是 ②,是「类名」且方法是实例方法就是 ③。

完整版教学

一、方法引用的由来:Lambda 的「转发」冗余

方法引用是 Lambda 的语法糖,要理解它先看一个现象——很多 Lambda 其实只是「把参数转发给一个已有方法」,没有任何额外逻辑:

// 这些 Lambda 都只是"调用一个方法",参数原样传过去,没有别的逻辑
names.forEach(s -> System.out.println(s));    // 只是调 println(s)
names.stream().map(s -> Integer.parseInt(s)); // 只是调 parseInt(s)
names.stream().map(s -> s.toUpperCase());     // 只是调 s.toUpperCase()

这种「Lambda 参数原封不动传给一个方法」的模式非常常见,但写 Lambda 还是有样板(s -> ...(s)s 出现两次)。方法引用的洞察是:既然这个 Lambda 只是「引用/转发到某个方法」,那干脆直接指向那个方法就行了

names.forEach(System.out::println);   // 直接引用 println 方法
names.stream().map(Integer::parseInt);
names.stream().map(String::toUpperCase);

所以方法引用的定位是:当 Lambda 体只是「调用一个已存在的方法」时的简写。它不是新功能,只是让「转发型 Lambda」更简洁。理解「方法引用 = 只调一个方法的 Lambda 的简写」,就理解了它的全部——它和 Lambda 完全等价,编译器会把它转成对应的 Lambda 处理。

二、第①种:静态方法引用

类名::静态方法——引用一个静态方法,参数传给它:

// Lambda:把 s 传给 Integer.parseInt
Function<String, Integer> f1 = s -> Integer.parseInt(s);
// 方法引用:直接引用 parseInt
Function<String, Integer> f2 = Integer::parseInt;

// 常见用法
list.stream().map(Integer::parseInt);      // 字符串转 int
list.stream().mapToInt(Integer::valueOf);
list.sort(Integer::compare);               // (a, b) -> Integer.compare(a, b)

这是最直观的一种——:: 左边是类名、右边是静态方法名,调用时把函数式接口方法的参数依次传给这个静态方法。它适用于「函数式接口的参数正好是某个静态方法的参数」的场景。判断标准:右边是静态方法parseIntvalueOfMath.max 等),就是静态方法引用。

三、第②种:特定对象的实例方法引用

对象::实例方法——:: 左边是一个具体的对象,引用它的实例方法:

// Lambda:调用 System.out 这个对象的 println 方法
Consumer<String> c1 = s -> System.out.println(s);
// 方法引用:System.out 是特定对象,引用它的 println
Consumer<String> c2 = System.out::println;

// 更多例子
String prefix = "Hello ";
Function<String, String> f = prefix::concat;   // s -> prefix.concat(s),prefix 是特定对象
List<String> list = new ArrayList<>();
Consumer<String> adder = list::add;            // s -> list.add(s),list 是特定对象

关键::: 左边是一个「已经存在的、确定的对象」System.outprefixlist),调用时在这个固定对象上调方法,函数式接口的参数作为方法的参数。这和第③种的区别就在这——②的对象是「外部一个确定的对象」,③的对象是「传进来的参数自己」。这种引用常用于「对某个固定对象反复调同一方法」的场景。

四、第③种:特定类型任意对象的实例方法引用(最易混)

类名::实例方法——:: 左边是类名,但引用的是实例方法,含义是「每个元素调用自己的这个方法」:

// Lambda:每个 s 调自己的 toUpperCase
Function<String, String> f1 = s -> s.toUpperCase();
// 方法引用:String::toUpperCase(每个 String 调自己的 toUpperCase)
Function<String, String> f2 = String::toUpperCase;

// 有参数的情况:第一个参数是接收者,其余是方法参数
Comparator<String> cmp = String::compareTo;   // (a, b) -> a.compareTo(b)
// a 是接收者(调 compareTo 的对象),b 是方法参数

这种最烧脑,因为 String::toUpperCase 形式上像静态方法引用(类名::方法),实际是实例方法。区分的关键是「右边的方法是静态的还是实例的」:

Integer::parseInt → parseInt 是静态方法 → 第①种(静态方法引用)
String::toUpperCase → toUpperCase 是实例方法 → 第③种(特定类型任意对象)

第③种的调用逻辑:
  函数式接口的"第一个参数"作为"方法的接收者"(调方法的那个对象)
  剩下的参数作为方法的参数
  String::compareTo:(a, b) → a.compareTo(b)  a是接收者、b是参数

理解要点:第③种里,:: 左边的类名只是「说明参数是什么类型」,真正调方法的对象是「传进来的第一个参数」。所以它表达「对每个(该类型的)对象,调用它自己的这个方法」。这在 Stream 里极常见(map(String::trim)map(User::getName))。

五、第④种:构造器引用

类名::new——引用构造器,用于「创建对象」的场景:

// Lambda:调用构造器
Supplier<List<String>> s1 = () -> new ArrayList<>();
// 构造器引用:ArrayList::new
Supplier<List<String>> s2 = ArrayList::new;

// 带参数的构造器引用
Function<String, StringBuilder> f = StringBuilder::new;  // s -> new StringBuilder(s)
// 收集到指定集合类型
Set<String> set = names.stream().collect(Collectors.toCollection(TreeSet::new));
// 用工厂方法创建数组
IntFunction<String[]> arrayMaker = String[]::new;   // n -> new String[n]

构造器引用 类名::new 表示「调用这个类的构造器创建实例」,函数式接口的参数传给对应的构造器。它常用于「需要创建对象的地方」——如 Stream.toArray(String[]::new) 生成数组、Collectors.toCollection(ArrayList::new) 指定收集容器、工厂方法。数组构造器引用 类型[]::new 是它的特殊形式。构造器引用让「创建对象」这件事也能用简洁的方法引用表达。

六、什么时候用方法引用,什么时候用 Lambda

方法引用不是「总是比 Lambda 好」,选择原则很清晰:

// ✓ 用方法引用:Lambda 只是"原样转发到一个方法",无额外逻辑
names.forEach(System.out::println);           // 比 s -> System.out.println(s) 简洁
names.stream().map(String::trim);             // 比 s -> s.trim() 简洁

// ✗ 别用方法引用(用 Lambda):有额外逻辑、参数变换、多步操作
names.stream().map(s -> s.trim().toUpperCase());   // 两步操作,方法引用表达不了
names.forEach(s -> System.out.println("Name: " + s));  // 有拼接逻辑,只能用 Lambda
names.stream().map(s -> Integer.parseInt(s) * 2);  // 有运算,方法引用表达不了

判断标准:Lambda 体是不是「原封不动调用一个已有方法」——是就用方法引用(更简洁、更能表达意图),有任何额外逻辑(参数变换、多步、拼接、运算)就用 Lambda。方法引用的可读性优势是「直接点明调用哪个方法、意图清晰」,但它只能表达「纯转发」,一旦有额外逻辑就用不了。所以两者不是替代关系,而是「纯转发用方法引用、有逻辑用 Lambda」的分工。

记忆钩子:「方法引用是『只调一个方法的 Lambda』的简写(:: 语法),四种:静态 类名::静态方法(parseInt)、特定对象 对象::方法(System.out::println)、特定类型任意对象 类名::实例方法(String::toUpperCase,每个元素调自己)、构造器 类名::new;②和③的区别看 :: 左边是具体对象还是类名;纯转发用方法引用、有额外逻辑用 Lambda」

七、常见误区与追问

  • 误区:方法引用是和 Lambda 不同的新功能。 它是 Lambda 的语法糖——当 Lambda 只是「调用一个已有方法」时的简写,两者完全等价,编译器把它转成对应的 Lambda 处理。
  • 误区:String::toUpperCase 是静态方法引用。 是第③种(特定类型任意对象的实例方法引用)——toUpperCase 是实例方法,含义是「每个 String 调自己的 toUpperCase」;区分看右边方法是静态还是实例。
  • 误区:方法引用总比 Lambda 好,应该都用。 只适合「原样转发到一个方法」的场景;有额外逻辑(参数变换、多步、拼接、运算)时表达不了,得用 Lambda。
  • 误区:对象::方法类名::方法 一样。 不同——对象::方法(②)是在一个确定的对象上调方法;类名::实例方法(③)是每个传入参数调自己的方法,接收者是参数本身。
  • 追问:方法引用的四种形式是什么? 静态方法引用(类名::静态方法)、特定对象的实例方法引用(对象::方法)、特定类型任意对象的实例方法引用(类名::实例方法)、构造器引用(类名::new)。
  • 追问:String::compareTo 是怎么对应 Lambda 的? 是第③种,等价于 (a, b) -> a.compareTo(b)——第一个参数 a 作为调方法的接收者,第二个参数 b 作为方法参数。
  • 追问:怎么区分第②种和第③种方法引用?:: 左边——是「具体对象」(如 System.out)就是②(在固定对象上调);是「类名」且方法是实例方法(如 String::trim)就是③(每个参数调自己)。

八、加强记忆

方法引用(Method Reference)是 Lambda 的进一步简化——当一个 Lambda 只是「原样调用一个已有方法」(参数转发、无额外逻辑)时,用 :: 直接引用那个方法(如 s -> System.out.println(s)System.out::println),它和 Lambda 完全等价(编译器转成对应 Lambda)。四种形式要记牢:① 静态方法引用 类名::静态方法Integer::parseInt);② 特定对象的实例方法引用 对象::方法System.out::println,在一个确定对象上调);③ 特定类型任意对象的实例方法引用 类名::实例方法String::toUpperCase每个参数调自己的方法,最易混,形式像静态但方法是实例的,第一个参数是接收者);④ 构造器引用 类名::newArrayList::newString[]::new)。②和③的区分:: 左边是「具体对象」就是②、是「类名+实例方法」就是③。①和③的区分:右边方法是静态就是①、是实例就是③。选择原则:Lambda 只是纯转发一个方法就用方法引用(简洁、意图清晰),有任何额外逻辑(变换、多步、拼接、运算)就用 Lambda。一句话「方法引用是只调一个方法的 Lambda 简写、四种(静态/特定对象/特定类型任意对象/构造器)、②③看左边是对象还是类名、纯转发才用」。