我目前正在深入研究像Lambdas和方法参考这样的Java 8
功能。玩了一下让我看到了以下例子:
public class ConsumerTest {
private static final String[] NAMES = {"Tony", "Bruce", "Steve", "Thor"};
public static void main(String[] args) {
Arrays.asList(NAMES).forEach(Objects::requireNonNull);
}
}
我的问题是:
为什么main方法中的行会编译?
如果我理解正确,那么引用的方法的签名必须与功能界面的SAM签名相对应。在这种情况下,消费者需要以下签名:
void accept(T t);
但是,requireNonNull
方法返回T
而不是void:
public static <T> T requireNonNull(T obj)
答案 0 :(得分:13)
Java语言规范版本8在15.13.2中说:
如果T是函数接口类型(§9.8),并且表达式与函数类型一致,则方法引用表达式在赋值上下文,调用上下文或具有目标类型T的转换上下文中是兼容的。地面目标类型来自T.
[..]
如果满足以下两个条件,则方法引用表达式与函数类型一致:
- 函数类型标识与引用对应的单个编译时声明。
- 以下之一是真的:
- 函数类型的结果为void。
- 函数类型的结果是R,并且将捕获转换(§5.1.10)应用于所选编译时声明的调用类型(§15.12.2.6)的返回类型的结果为R '(其中R是可用于推断R'的目标类型),R和R'都不是空的,R'在赋值上下文中与R兼容。
(强调我的)
因此,函数类型的结果为void,足以让它匹配。
JLS 15.12.2.5还特别提到匹配方法时使用void。对于lambda表达式,存在 void-compatible 块(15.27.2)的概念,该块在15.12.2.1中引用,但没有方法引用的等效定义。
我一直无法找到更具体的解释(但JLS是一个难以破解的难题,所以也许我错过了一些相关部分),但我认为这与你也被允许的事实有关自己调用非void方法作为语句(没有赋值,return
等)。)
JLS 15.13.3 Run-Time Evaluation of Method References也说:
为了确定编译时结果,如果调用方法的结果为void,则方法调用表达式是表达式语句;如果调用方法的结果是非void,则返回语句的表达式。
当方法引用的编译时声明是签名多态时,此确定的效果是:
- 方法调用的参数类型是相应参数的类型。
- 方法调用无效或返回类型为Object,具体取决于包含方法调用的调用方法是无效还是具有返回类型。
因此,生成的方法调用将为void以匹配函数类型。
答案 1 :(得分:3)
除了它在@Mark Rotteveel的确切答案中所说的,它编译是因为在Java中你可以忽略任何方法调用的结果,例如在下面的例子中:
Map<String, String> map = new HashMap<>();
map.put("1", "a");
map.put("1", "A"); // Who cares about the returned value "a"?
由于您未在forEach()
消费者区块中返回任何内容,因此根据规范有效。