众所周知,在进行累积时,“reduce”总是返回一个新的不可变对象,而“collect”将对可变对象进行更改。
但是,当我不小心为reduce和collect方法分配一个方法引用时,它编译时没有任何错误。为什么呢?
看看以下代码:
public class Test {
@Test
public void testReduce() {
BiFunction<MutableContainer,Long,MutableContainer> func =
MutableContainer::reduce;
// Why this can compile?
BiConsumer<MutableContainer,Long> consume =
MutableContainer::reduce;
// correct way:
//BiConsumer<MutableContainer,Long> consume =
// MutableContainer::collect;
long param=10;
MutableContainer container = new MutableContainer(0);
consume.accept(container, param);
// here prints "0",incorrect result,
// because here we expect a mutable change instead of returning a immutable value
System.out.println(container.getSum());
MutableContainer newContainer = func.apply(container, param);
System.out.println(newContainer.getSum());
}
}
class MutableContainer {
public MutableContainer(long sum) {
this.sum = sum;
}
public long getSum() {
return sum;
}
public void setSum(long sum) {
this.sum = sum;
}
private long sum;
public MutableContainer reduce(long param) {
return new MutableContainer(param);
}
public void collect(long param){
this.setSum(param);
}
}
答案 0 :(得分:5)
基本上,问题简化为:BiConsumer
是一个功能接口,其功能声明如下:
void accept(T t, U u)
您已使用正确的参数为其提供了方法参考,但返回类型错误:
public MutableContainer reduce(long param) {
return new MutableContainer(param);
}
[T
参数实际上是调用this
时的reduce
对象,因为reduce
是实例方法而不是静态方法。这就是参数正确的原因。]但是,返回类型为MutableContainer
而不是void
。所以问题是,为什么编译器会接受它?
直觉上,我认为这是因为方法引用或多或少等同于一个看起来像这样的匿名类:
new BiConsumer<MutableContainer,Long>() {
@Override
public void accept(MutableContainer t, Long u) {
t.reduce(u);
}
}
请注意,t.reduce(u)
将返回结果。但是,结果被丢弃了。既然可以调用带有结果的方法并丢弃结果,我认为,通过扩展,可以使用方法返回结果的方法引用,对于其方法返回void
的函数接口。
从法律上讲,我认为其原因在于JLS 15.12.2.5。这部分很难,我不完全理解它,但在本节的某个地方它说
如果e是精确的方法参考表达式... R 2 是 空隙。
其中,如果我正确读取,R 2 是功能接口方法的结果类型。我认为这是允许在需要void方法引用的情况下使用非void方法引用的子句。
(编辑:正如Ismail在评论中指出的那样,JLS 15.13.2可能是这里的正确条款;它讨论的方法引用与函数类型是一致的,其中一个是条件是函数类型的结果是void
。)
无论如何,这应该有希望解释为什么编译。当然,编译器不能总是告诉你什么时候会做出会产生错误结果的事情。