为什么这个Java 8方法参考编译?

时间:2015-09-12 12:58:13

标签: java java-8 method-reference

我目前正在深入研究像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)

2 个答案:

答案 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()消费者区块中返回任何内容,因此根据规范有效。