为什么 StringBuilder::append 是 BiConsumer<StringBuilder, String>?

6
我发现了一个有趣的使用Stream API的示例:
Stream<String> stream = Stream.of("w", "o", "l", "f");
BiConsumer<StringBuilder, String> append = StringBuilder::append;
StringBuilder collected = stream.collect(StringBuilder::new, append, StringBuilder::append);
System.out.println(collected); //it works correctly

Stream.collect需要三个参数:

Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner

BiConsumer接受两个参数,不返回任何内容。为什么这行代码编译并且可以正常工作?

BiConsumer<StringBuilder, String> append = StringBuilder::append;

StringBuilder没有名为append(java.lang.StringBuilder, java.lang.String)的无返回值方法。


5
第一个参数成为方法被调用的对象。StringBuilder.append(String) 确实存在。 - Tunaki
2
有一个可以帮助我理解的事情是,成员函数与具有额外参数this的静态函数没有什么不同。每当您看到 ClassName::memberFunction 时,第一个参数始终为ClassName this - Hank D
在所提到的重复问题中,我没有看到答案。有人可以详细说明一下吗?我也遇到了同样的问题。由于append不是一个void方法,而BiConsumer的非抽象方法是一个void方法,那么上述代码是如何编译的? - Asiri Liyana Arachchi
2个回答

3

JLS 15.13.3指定了接收器(即方法被调用的对象)可以成为函数式接口的第一个参数:

如果形式为ReferenceType :: [TypeArguments] Identifier,则调用方法的主体同样具有方法调用表达式的效果,该方法调用表达式是编译时声明的方法引用表达式。方法调用表达式的运行时评估如§15.12.4.3、§15.12.4.4和§15.12.4.5中所述,其中:

调用模式是根据§15.12.3中指定的编译时声明派生的。

如果编译时声明是实例方法,则目标引用是调用方法的第一个形式参数。否则,没有目标引用。

如果编译时声明是实例方法,则方法调用表达式的参数(如果有)是调用方法的第二个及其后续形式参数。否则,方法调用表达式的参数是调用方法的形式参数。

编译时声明实际上是一个实例方法,因此 StringBuilder 成为调用方法的第一个参数,而 String 成为第二个参数。
换句话说,方法引用 SomeClass::instanceMethod 等同于 lambda 表达式 (SomeClass receiver, args...) -> receiver.instanceMethod(args...)

2
BiConsumer 的第一个类型参数是该方法应用的类型,第二个类型参数是方法的单个参数。 经典示例是 ArrayList::add

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接