Lambda 表达式必须严格遵循函数式接口方法的签名契约,包括返回类型、参数列表和异常声明;因 Consumer 等接口方法未声明 throws IOException,故 lambda 中调用 Files.readString 会编译失败。

Lambda 表达式本身不定义方法签名,它只是函数式接口抽象方法的**实现体**。编译器在解析 lambda 时,会严格比对它的行为是否满足该接口方法的完整契约——包括返回类型、参数列表,以及 是否允许抛出指定类型的异常。
函数式接口的方法签名是硬性契约
JDK 提供的常用函数式接口,比如:
-
Consumer<T>.accept(T)—— 返回 void,无 throws -
Function<T,R>.apply(T)—— 返回 R,无 throws -
Runnable.run()—— 返回 void,无 throws
这些方法在字节码层面就明确声明了“不抛受检异常”。Lambda 写成 x -> Files.readString(Paths.get(x)),虽然只有一行,但调用了会抛 IOException 的 API。编译器发现:目标接口没说能抛这个异常,而 IOException 又必须被声明或捕获——于是直接拒绝编译,不运行、不推断、不妥协。
不是 Lambda 限制,而是类型系统在执行接口一致性检查
你可以把 lambda 理解为一个匿名类的语法糖:
list.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
Files.readString(Paths.get(s)); // 编译报错:Unhandled exception
}
});
这个匿名类重写 accept 方法时,也不能擅自加 throws IOException,否则就违反了 Consumer 接口定义。Lambda 同理——它必须“守约”,不能扩大异常范围。
受检异常必须显式出现在方法签名里才能向外传递
Java 要求所有受检异常(Exception 及其子类,但排除 RuntimeException)必须满足以下任一条件:
- 在当前方法签名中用
throws声明 - 在方法体内用
try-catch捕获并处理
lambda 没有独立的方法签名,它依附于函数式接口方法。接口方法没写 throws,lambda 就失去了“合法出口”,只能自己吞掉或转成非受检异常。
为什么 RuntimeException 可以直接 throw
因为 RuntimeException 及其子类属于非受检异常,Java 编译器不要求强制声明或捕获。所以写 x -> { if (x == null) throw new NullPointerException(); } 完全合法——这不是“绕过”,而是规则本来就没约束它。

















