为什么这个 Java 8 方法参考编译?
Why does this Java 8 method reference compile?
我目前正在深入研究 Java 8
功能,例如 Lambda 和方法引用。玩了一下让我想到了以下示例:
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
方法 returns T
而不是 void:
public static <T> T requireNonNull(T obj)
Java 语言规范版本 8 在 15.13.2 中说:
A method reference expression is compatible in an assignment context, invocation context, or casting context with a target type T if T is a functional interface type (§9.8) and the expression is congruent with the function type of the ground target type derived from T.
[..]
A method reference expression is congruent with a function type if both of the following are true:
- The function type identifies a single compile-time declaration corresponding to the reference.
- One of the following is true:
- The result of the function type is void.
- The result of the function type is R, and the result of applying capture conversion (§5.1.10) to the return type of the invocation type (§15.12.2.6) of the chosen compile-time declaration is R' (where R is the target type that may be used to infer R'), and neither R nor R' is void, and R' is compatible with R in an assignment context.
(强调我的)
因此函数类型的结果为 void 这一事实足以让它匹配。
JLS 15.12.2.5 also specifically mentions the use of void when matching methods. For lambda expression there is the notion of a void-compatible block (15.27.2) which is referenced in 15.12.2.1,但没有对方法引用的等效定义。
我没能找到更具体的解释(但 JLS 是一个棘手的问题,所以也许我遗漏了一些相关部分),但我认为这与你是还允许调用非 void 方法作为其自身的语句(无赋值,return
等))。
JLS 15.13.3 Run-Time Evaluation of Method References 还说:
For the purpose of determining the compile-time result, the method invocation expression is an expression statement if the invocation method's result is void, and the Expression of a return statement if the invocation method's result is non-void.
The effect of this determination when the compile-time declaration of the method reference is signature polymorphic is that:
- The types of the parameters for the method invocation are the types of the corresponding arguments.
- The method invocation is either void or has a return type of Object, depending on whether the invocation method which encloses the method invocation is void or has a return type.
因此生成的方法调用将无效以匹配功能类型。
除了它在@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()
消费者块中返回任何内容,因此根据规范它是有效的。
我目前正在深入研究 Java 8
功能,例如 Lambda 和方法引用。玩了一下让我想到了以下示例:
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
方法 returns T
而不是 void:
public static <T> T requireNonNull(T obj)
Java 语言规范版本 8 在 15.13.2 中说:
A method reference expression is compatible in an assignment context, invocation context, or casting context with a target type T if T is a functional interface type (§9.8) and the expression is congruent with the function type of the ground target type derived from T.
[..]
A method reference expression is congruent with a function type if both of the following are true:
- The function type identifies a single compile-time declaration corresponding to the reference.
- One of the following is true:
- The result of the function type is void.
- The result of the function type is R, and the result of applying capture conversion (§5.1.10) to the return type of the invocation type (§15.12.2.6) of the chosen compile-time declaration is R' (where R is the target type that may be used to infer R'), and neither R nor R' is void, and R' is compatible with R in an assignment context.
(强调我的)
因此函数类型的结果为 void 这一事实足以让它匹配。
JLS 15.12.2.5 also specifically mentions the use of void when matching methods. For lambda expression there is the notion of a void-compatible block (15.27.2) which is referenced in 15.12.2.1,但没有对方法引用的等效定义。
我没能找到更具体的解释(但 JLS 是一个棘手的问题,所以也许我遗漏了一些相关部分),但我认为这与你是还允许调用非 void 方法作为其自身的语句(无赋值,return
等))。
JLS 15.13.3 Run-Time Evaluation of Method References 还说:
For the purpose of determining the compile-time result, the method invocation expression is an expression statement if the invocation method's result is void, and the Expression of a return statement if the invocation method's result is non-void.
The effect of this determination when the compile-time declaration of the method reference is signature polymorphic is that:
- The types of the parameters for the method invocation are the types of the corresponding arguments.
- The method invocation is either void or has a return type of Object, depending on whether the invocation method which encloses the method invocation is void or has a return type.
因此生成的方法调用将无效以匹配功能类型。
除了它在@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()
消费者块中返回任何内容,因此根据规范它是有效的。