为什么具有返回类型的 Java 方法引用与使用者接口匹配?

2022-09-01 17:06:49

我对以下代码感到困惑

class LambdaTest {
    public static void main(String[] args) {
        Consumer<String>         lambda1 = s -> {};
        Function<String, String> lambda2 = s -> s;

        Consumer<String>         lambda3 = LambdaTest::consume; // but s -> s doesn't work!
        Function<String, String> lambda4 = LambdaTest::consume;
    }

    static String consume(String s) { return s;}
}

我本来以为lambda3的分配会失败,因为我的消费方法与消费者接口中的接受方法不匹配 - 返回类型是不同的,字符串与无效。

此外,我一直认为 Lambda 表达式和方法引用之间存在一对一的关系,但正如我的示例所示,情况显然并非如此。

有人可以向我解释这里发生了什么吗?


答案 1

正如Brian Goetz在评论中指出的那样,设计决策的基础是允许将方法适应功能接口,就像调用方法一样,即您可以调用每个值返回方法并忽略返回值。

当涉及到 lambda 表达式时,事情会变得更加复杂。lambda 表达式有两种形式,以及 。(args) -> expression(args) -> { statements* }

第二种形式是否兼容,取决于没有代码路径是否尝试返回值的问题,例如 不兼容,但表达式兼容,而 或 兼容。请注意,和 是兼容的和值兼容的,因为它们不能正常完成并且没有语句。void() -> { return ""; }void() -> {}() -> { return; }void() -> { for(;;); }() -> { throw new RuntimeException(); }voidreturn

如果表达式的计算结果为值,则该表单与值兼容。但也有表达式,它们同时是语句。这些表达式可能有副作用,因此可以写成独立语句,仅用于产生副作用,忽略产生的结果。同样,如果表达式也是语句,则表单可以是兼容的。(arg) -> expression(arg) -> expressionvoid

表单的表达式不能兼容,因为不是语句,即你也不能写。另一方面,它可以是兼容的,因为方法调用是语句。类似地,可以兼容,因为增量可以用作语句,因此也是有效的。当然,必须是一个字段才能使它起作用,而不是局部变量。s -> svoidss -> { s; }s -> s.toString()voids -> i++voids -> { i++; }i

Java 语言规范 §14.8.表达式语句列出了可用作语句的所有表达式。除了已经提到的方法调用和递增/递减运算符之外,它还命名了赋值和类实例创建表达式,因此也是兼容的。s -> foo=ss -> new WhatEver(s)void

作为旁注,表单是唯一不与值兼容的表达式形式。(arg) -> methodReturningVoid(arg)


答案 2

consume(String)方法匹配接口,因为它使用 - 它返回值的事实是无关紧要的,因为 - 在这种情况下 - 它只是被忽略。(因为接口根本不期望任何返回值)。Consumer<String>StringConsumer

它必须是一个设计选择,基本上是一个实用程序:想象一下,必须重构或复制多少方法才能满足功能接口(如甚至非常常见的)的需求。(请注意,您可以将任何不消耗参数的方法作为 a 传递给 ,例如。ConsumerRunnableRunnableExecutor

甚至像返回值这样的方法: 。仅仅因为它们返回了一些东西(在许多情况下大多是无关紧要的)而无法传递这样的方法引用,这将是相当烦人的。java.util.List#add(Object)boolean


推荐