OCPJP 8(IZ0-809)中Consumer与Supplier的Lambda表达式疑问
这个问题问到了Java Lambda表达式中很容易混淆的一个点——语句表达式和普通表达式的区别,以及函数式接口上下文如何解析Lambda体。咱们一步步拆解:
先明确两个核心前提
- Consumer和Supplier的接口定义:
Supplier<T>的核心方法是T get():必须返回一个T类型的值,所以Lambda体必须能产生这个返回值。Consumer<T>的核心方法是void accept(T t):不需要返回值,Lambda体只要能执行即可,不需要产生返回值(或者说编译器会忽略返回值)。
- Lambda体的两种形式:
- 表达式体:没有大括号,比如
() -> new Object() - 语句块体:有大括号,比如
() -> { new Object(); }
- 表达式体:没有大括号,比如
为什么a->new Pregunta24(a)能作为Consumer的Lambda?
new Pregunta24(a)是类实例创建表达式,属于Java中的「语句表达式」——也就是可以单独作为一条语句执行的表达式(比如你在代码里单独写一行new Pregunta24(a);是完全合法的)。
对于返回void的函数式接口(比如Consumer),如果Lambda体是表达式体,只要这个表达式是「语句表达式」,编译器就会把它当作语句执行,自动忽略它的返回值,完全符合void accept的要求,所以不会报错。
为什么a->1会报错?
1是一个字面量表达式,不属于「语句表达式」的范畴。当你把它作为Consumer的Lambda表达式体时,编译器会认为你试图返回这个int值,但Consumer的accept方法要求返回void,类型不匹配,因此抛出「incompatible types: bad return type in lambda expression」错误。
如果改成语句块体a->{1;}就会编译通过,因为在语句块里,任何表达式都可以当作语句执行,编译器会忽略它的返回值。
为什么String::length不能作为Consumer?
方法引用的匹配是严格遵循函数签名的:
Consumer<String>的accept方法签名是void accept(String t)String::length对应的是实例方法String.length(),它的签名是int length()——接受0个参数,返回int值。
两者的签名完全不匹配:Consumer需要接受一个String参数且返回void,而length()不需要参数且返回int。哪怕你想让编译器忽略返回值也不行,因为方法引用的签名必须和函数式接口的方法签名兼容,所以会报错。
再对比Supplier的例子
() -> new Pregunta24("HI")作为Supplier的Lambda,因为Supplier的get()需要返回Pregunta24类型,而类实例创建表达式正好返回这个类型,完美匹配,所以编译通过。
总结一下:
- 对于返回void的函数式接口,Lambda表达式体如果是表达式,必须是「语句表达式」(比如new对象、调用void方法、赋值等);如果是语句块,则可以放任何语句。
- 普通字面量/非语句表达式不能直接作为返回void的Lambda的表达式体,因为编译器会认为你试图返回值,导致类型不匹配。
内容的提问来源于stack exchange,提问作者chiperortiz

