方法参考 - 将函数传递给带有消费者参数的方法

Method Reference - passing Function to method with Consumer argument

我正在学习 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 returns 未绑定方法引用,您可以通过将类型 Holder 的对象作为参数传递来调用它。像这样:getHolded.apply(holder)

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

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

当您声明 consume(Holder::getHolded); 时,编译器(根据所谓的 特殊无效兼容性规则 )会将其推断为 Consumer<Holder>.

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

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

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

因此你的例子可以简化为:

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 毕竟。

Java 8 在包java.util.function中引入了4个重要的"function shapes"。

  • Consumer -> 接受一个方法引用(或 lambda 表达式),它接受一个参数但不 return 任何东西
  • Supplier -> 接受不带参数的方法引用(或 lambda 表达式)并且 return 是一个对象。
  • Function -> 接受一个带有一个参数的方法引用(或 lambda 表达式),return 是一个对象。
  • Predicate -> 接受一个带有一个参数的方法引用(或 lambda 表达式),return 是一个布尔值。

阅读 Java docs 了解更多详情。

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

第二条语句

consume(getHolded);

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

第一个语句

consume(Holder::getHolded);

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

Consumer<Holder> consumer = Holder::getHolded;

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

Function<Holder, String> getHolded = Holder::getHolded;

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