14

我目前正在深入研究Java 8Lambda 和方法引用等功能。玩了一下让我想到了以下示例:

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)
4

2 回答 2

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' 都不是 void,并且 R' 在赋值上下文中与 R 兼容。

(强调我的)

所以函数类型的结果是 void 的事实足以让它匹配。

JLS 15.12.2.5还特别提到了在匹配方法时使用 void。对于 lambda 表达式,存在 15.12.2.1 中引用的void 兼容块 ( 15.27.2 ) 的概念,但方法引用没有等效定义。

我还没有找到更具体的解释(但 JLS 很难破解,所以我可能遗漏了一些相关部分),但我认为这与您也可以调用 non -void 方法作为自己的语句(没有赋值return等))。

JLS 15.13.3 方法参考的运行时评估还说:

为了确定编译时结果,如果调用方法的结果为void,则方法调用表达式为表达式语句,如果调用方法的结果为非void,则为return语句的表达式。

当方法引用的编译时声明是签名多态时,此确定的效果是:

  • 方法调用的参数类型是相应参数的类型。
  • 方法调用要么是 void,要么返回类型为 Object,这取决于包含方法调用的调用方法是 void 还是具有返回类型。

所以生成的方法调用将是无效的以匹配函数类型。

于 2015-09-12T13:09:22.797 回答
4

除了它在@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()消费者块中返回任何内容,因此它根据规范是有效的。

于 2015-09-12T13:47:11.513 回答