【问题标题】:Method Reference - passing Function to method with Consumer argument方法参考 - 将函数传递给具有消费者参数的方法
【发布时间】:2018-05-16 18:28:03
【问题描述】:

我正在学习 Java 8 中的方法引用,但我很难理解为什么它会起作用?

class Holder {
    private String holded;

    public Holder(String holded) {
        this.holded = holded;
    }

    public String getHolded() {
        return holded;
    }
}

private void run() {
    Function<Holder, String> getHolded = Holder::getHolded;

    consume(Holder::getHolded); //This is correct...
    consume(getHolded);         //...but this is not
}

private void consume(Consumer<Holder> consumer) {
    consumer.accept(null);
}

正如您在 run 方法中看到的 - Holder::getHolded 返回未绑定的方法引用,您可以通过将 Holder 类型的对象作为参数传递来调用它。像这样:getHolded.apply(holder)

但是为什么当它被直接作为方法参数调用时它会将这个未绑定的方法引用转换为Consumer,而当我显式传递Function 时它不这样做呢?

【问题讨论】:

  • 请注意,您还应该能够consumer(getHolded::apply)
  • 顺便说一句,“持有”应该是“持有”。
  • @Holger :) 你无处不在!我什至没有注意到,太好了!
  • consumer(getHolded::apply) 将完全符合consume(Holder::getHolded) 提出的相同无效兼容性规则;)@Eugene 下面提到了该规则

标签: java-8 method-reference functional-interface


【解决方案1】:

这里有两件事,lambda 表达式是 poly 表达式 - 它们是由编译器使用其上下文(例如泛型)推断

当您声明consume(Holder::getHolded); 时,编译器(根据所谓的特殊的void 兼容性规则)会将其推断为Consumer&lt;Holder&gt;

这可能看起来并不明显,但请考虑一个简化的示例。调用一个方法并丢弃它的返回类型通常是可以的,对吧?例如:

List<Integer> list = new ArrayList<>();
list.add(1);

即使list.add(1) 返回一个布尔值,我们也不关心它。

因此,您的有效示例可以简化为:

consume(x -> {
        x.getHolded(); // ignore the result here
        return;
});

所以这些都是可能且有效的声明:

Consumer<Holder> consumer = Holder::getHolded;
Function<Holder, String> function = Holder::getHolded;

但在这种情况下,我们明确告诉Holder::getHolded是什么类型,这不是编译器推断的,因此consume(getHolded);失败了,毕竟Consumer!=Function

【讨论】:

  • 谢谢!这真的帮助我理解了这里发生的整个摇摇晃晃的事情。尤其是特殊的 void 兼容性规则
  • @TomaszBielaszewski 只是注意到并非所有内容都符合该规则,如果我没记错的话,只有 4 种类型,方法调用、构造函数调用、递增/递减,我忘记了最后一个 :) 搜索 JLS如果你愿意...
【解决方案2】:

Java 8 在包 java.util.function 中引入了 4 个重要的“函数形状”。

  • Consumer -> 接受一个方法引用(或 lambda 表达式),它接受一个参数但不返回任何内容
  • Supplier -> 接受不带参数并返回对象的方法引用(或 lambda 表达式)。
  • 函数 -> 接受一个方法引用(或 lambda 表达式),它接受一个参数并返回一个对象。
  • 谓词 -> 接受一个方法引用(或 lambda 表达式),它接受一个参数并返回一个布尔值。

阅读Java docs了解更多详情。

要回答您关于为什么第一个有效但第二个出错的问题,请阅读以下内容:

第二个说法

consume(getHolded);

不起作用,因为参数getHolded 的类型是Function&lt;Holder, String&gt;,而consume 方法需要Consumer&lt;Holder&gt; 类型的参数。由于FunctionConsumer 之间没有父子关系,因此它需要显式转换,否则编译器会正确地出错。

第一句话

consume(Holder::getHolded);

之所以有效是因为getHolded 方法被声明为public String getHolded(),这意味着它不接受任何参数并返回String。根据新的void compatibility 规则,void 类型被推断为包含引用方法的类。考虑以下语句:

Consumer&lt;Holder&gt; consumer = Holder::getHolded;

即使getHolded 方法不接受任何参数,这也是一个有效的语句。允许这有助于推断 void 类型。还有一个例子是你自己提到的那个:

Function&lt;Holder, String&gt; getHolded = Holder::getHolded;

这也是一个有效的陈述,您说过函数对象getHolded 是一个Function,它返回String 并接受Holder 类型,即使分配的方法引用不带任何参数。

【讨论】:

    猜你喜欢
    • 2016-12-18
    • 1970-01-01
    • 2019-08-30
    • 1970-01-01
    • 2011-07-23
    • 2017-01-05
    • 2013-10-04
    • 2013-05-19
    相关资源
    最近更新 更多