← 返回面试专题导航

🔧 Lambda 表达式面试题

从匿名内部类到函数式编程的演进

本页系统讲解 Lambda 语法、函数式接口、方法引用、实际项目中的使用技巧。

🎯 难度筛选

一、基础语法

1. 什么是 Lambda 表达式?解决了什么问题? 简单

定义:Lambda 是一种更简洁的写法,用来表示只包含一个抽象方法的接口实例(函数式接口)。

解决的问题:

  • 简化匿名内部类写法
  • 让函数可以作为参数传递
  • 为 Stream API 等提供基础
// 传统写法:匿名内部类 new Thread(new Runnable() { @Override public void run() { System.out.println("Hello"); } }).start(); // Lambda 写法 new Thread(() -> System.out.println("Hello")).start(); // 形式: (参数列表) -> { 方法体 } // 例: (a, b) -> a + b
2. Lambda 的几种常见语法形式? 中等
// 1. 无参数,无返回值 Runnable r1 = () -> System.out.println("hello"); // 2. 有一个参数,无返回值 Consumer c1 = (s) -> System.out.println(s); Consumer c2 = s -> System.out.println(s); // 参数只有一个时可以省略括号 // 3. 有多个参数,有返回值 BinaryOperator add1 = (a, b) -> { return a + b; }; BinaryOperator add2 = (a, b) -> a + b; // 方法体只有一行时可以省略 return 和大括号 // 4. 指定参数类型 BinaryOperator add3 = (Integer a, Integer b) -> a + b; // 一般省略 // 5. 复杂 Lambda Function length = s -> { System.out.println("calc length"); return s.length(); };
💡 规律:能省则省,但保持可读性,特别是在面试写白板代码时。

二、函数式接口

3. 什么是函数式接口?常见的函数式接口有哪些? 中等

函数式接口:只包含一个抽象方法的接口,可以有默认方法和静态方法。

@FunctionalInterface public interface MyFunc { void run(); // 只能有一个抽象方法 default void log() { System.out.println("default"); } static void util() { System.out.println("static"); } }

JDK 内置常见函数式接口(java.util.function):

  • Supplier<T>:无参,有返回值
  • Consumer<T>:有参,无返回值
  • Function<T,R>:有参,有返回值
  • Predicate<T>:断言,返回 boolean
  • UnaryOperator<T>:一元操作,输入输出同类型
  • BinaryOperator<T>:二元操作,两个参数同类型
4. 如何自定义函数式接口并配合 Lambda 使用? 中等
@FunctionalInterface interface Calculator { int calc(int a, int b); } public class LambdaDemo { public static void main(String[] args) { // 1. 使用 Lambda 实现 Calculator add = (a, b) -> a + b; Calculator multiply = (a, b) -> a * b; // 2. 作为参数传递 int result = operate(10, 20, add); System.out.println(result); // 30 } public static int operate(int a, int b, Calculator c) { return c.calc(a, b); } }

注意:接口上加 @FunctionalInterface 不是必须的,但可以在编译期帮你检查是否只有一个抽象方法。

三、方法引用与构造器引用

5. 什么是方法引用?有哪些形式? 中等

方法引用:是对 Lambda 的进一步简化,用 :: 引用已有方法。

四种形式:

List list = Arrays.asList("b", "a", "c"); // 1. 静态方法引用:ClassName::staticMethod Collections.sort(list, String::compareToIgnoreCase); // 2. 特定对象的实例方法引用:instance::method PrintStream out = System.out; list.forEach(out::println); // 3. 特定类型任意对象的实例方法引用:ClassName::method Function func = String::length; // 等价于 s -> s.length() // 4. 构造器引用:ClassName::new Supplier> listSupplier = ArrayList::new; List newList = listSupplier.get();
6. 方法引用和 Lambda 的参数匹配规则是什么? 困难

核心规则:方法引用的参数列表和返回值必须与目标函数式接口的抽象方法兼容。

// 函数式接口 @FunctionalInterface interface Converter { T convert(F from); } // 目标方法 public class NumberUtils { public static Integer parse(String s) { return Integer.parseInt(s); } } // 方法引用 Converter c1 = NumberUtils::parse; // 等价于 s -> NumberUtils.parse(s) // 如果方法签名不匹配,会编译错误

四、Lambda 的注意事项

7. Lambda 中对外部变量的访问有什么限制? 中等

规则:Lambda 可以访问有效 final 的外部局部变量。

public void test() { int x = 10; // 有效 final:后面没有修改 Runnable r = () -> { System.out.println(x); // 允许 }; // x = 20; // 编译错误:x 被 Lambda 捕获,必须是 final 或有效 final } // 原因: // JVM 会将 x 拷贝一份到 Lambda 对象中,修改外部变量会导致不一致
8. Lambda 和匿名内部类在 this、变量捕获上的区别? 中等
public class Demo { public void test() { Runnable r1 = new Runnable() { @Override public void run() { System.out.println(this.getClass().getName()); } }; Runnable r2 = () -> { System.out.println(this.getClass().getName()); }; } } // 输出分析: // 匿名内部类中的 this:指向匿名内部类实例 // Lambda 中的 this:指向外部类 Demo 的实例

区别总结:

  • 匿名内部类会生成一个新的类;Lambda 可能不会(使用 invokedynamic)。
  • 匿名内部类的 this 指向自己;Lambda 的 this 指向外部类。
9. Lambda 在序列化、调试上的坑有哪些? 困难
  • 序列化:Lambda 默认不可序列化,除非函数式接口 extends Serializable。
  • 栈追踪:Lambda 的方法名是合成的,看起来不如普通方法直观。
  • 版本兼容:序列化的 Lambda 在不同 JDK 版本间可能不兼容。
@FunctionalInterface interface SerializableFunc extends java.io.Serializable { void run(); } SerializableFunc f = () -> System.out.println("ok"); // 现在 f 可以被序列化
10. 实际项目中如何合理使用 Lambda? 简单
  • 适合用于短小逻辑策略抽象(如排序规则、过滤条件)。
  • 不要把复杂业务逻辑全部写在 Lambda 里,影响可读性和调试。
  • 配合 Stream、CompletableFuture 使用效果最佳。
// 不推荐:业务逻辑太复杂 orderList.stream() .filter(o -> { // 很多 if-else,调用多个 service return complexCheck(o); }) .collect(Collectors.toList()); // 推荐:提取为命名方法 orderList.stream() .filter(this::validOrder) .collect(Collectors.toList());