Java8收集与减少

Java8收集与减少,java,java-8,reduce,collect,Java,Java 8,Reduce,Collect,众所周知,在进行累加时,“reduce”总是返回一个新的不可变对象,而“collect”将对可变对象进行更改 然而,当我意外地将一个方法引用分配给reduce和collect方法时,它编译时没有任何错误。为什么? 请看下面的代码: public class Test { @Test public void testReduce() { BiFunction<MutableContainer,Long,MutableContainer> func =

众所周知,在进行累加时,“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);
    }
}
公共类测试{
@试验
公共void testReduce(){
双函数函数=
可变容器::reduce;
//为什么这个可以编译?
双消费者消费=
可变容器::reduce;
//正确的方法:
//双消费者消费=
//可变容器::收集;
长参数=10;
MutableContainer=新的MutableContainer(0);
消费.接受(容器,参数);
//此处打印“0”,结果不正确,
//因为这里我们期望一个可变的变化,而不是返回一个不变的值
System.out.println(container.getSum());
MutableContainer newContainer=func.apply(container,param);
System.out.println(newContainer.getSum());
}
}
类可变容器{
公共可变容器(长和){
this.sum=sum;
}
公共长getSum(){
回报金额;
}
公共无效设置金额(长期金额){
this.sum=sum;
}
私人长款;
公共可变容器reduce(长参数){
返回新的可变容器(param);
}
公共无效收集(长参数){
此参数为setSum(param);
}
}

基本上,问题归结为:
BiConsumer
是一个功能接口,其功能声明如下:

void accept(T t, U u)
new BiConsumer<MutableContainer,Long>() {
    @Override
    public void accept(MutableContainer t, Long u) {
         t.reduce(u);
     }
}
您已经为它提供了一个具有正确参数的方法引用,但返回类型错误:

public MutableContainer reduce(long param) {
    return new MutableContainer(param);
}
[调用
reduce
时,
T
参数实际上就是
这个
对象,因为
reduce
是一个实例方法,而不是静态方法。这就是参数正确的原因。]返回类型是
MutableContainer
而不是
void
。所以问题是,为什么编译器会接受它

直觉上,我认为这是因为方法引用或多或少相当于一个匿名类,如下所示:

void accept(T t, U u)
new BiConsumer<MutableContainer,Long>() {
    @Override
    public void accept(MutableContainer t, Long u) {
         t.reduce(u);
     }
}
newbiconsumer(){
@凌驾
公共无效接受(可变容器t,长u){
t、 减少(u);
}
}
请注意,
t.reduce(u)
将返回一个结果。但是,结果将被丢弃。既然可以用一个结果调用一个方法并丢弃结果,我认为,从广义上讲,这就是为什么对于一个函数接口,如果该函数接口的方法返回
void
,那么在该函数接口中使用一个方法引用(该方法返回结果)是可以的

从法律上讲,我认为原因在于。这一节很难,我不完全理解,但在这一节的某个地方,它说

如果e是一个精确的方法引用表达式。。。R2是 空虚

其中,如果我理解正确,R2是函数接口方法的结果类型。我认为这是一条允许在预期使用无效方法引用的情况下使用非无效方法引用的条款

(编辑:正如Ismail在评论中指出的,这里的子句可能是正确的;它谈到方法引用与函数类型一致,其中一个条件是函数类型的结果是
void


无论如何,这应该可以解释为什么它会编译。当然,编译器不能总是告诉你什么时候做的事情会产生不正确的结果。

我认为在这种情况下更相关--“方法引用表达式在赋值上下文[…]中与目标类型t[…]兼容,如果[…]函数类型[Of t]的结果无效”ok,我认为解释相当清楚。不确定为什么会被接受,因为在大多数情况下它会导致bug。强类型检查应该可以防止这种错误。你的动机是正确的。正如您可以调用方法并放弃返回类型一样,您也可以将带有结果的方法转换为无效的返回函数接口。前一位评论者担心这种灵活性会带来bug,但另一种情况更糟——你甚至不能将
aList::add
转换为
消费者,因为
add
会返回一些东西——如果你不能说
x.forEach(list::add)
,这将非常烦人!任何一种解决方案都会惹恼某人。这种方式被认为是邪恶中较小的一种。“真的,这甚至不是一个势均力敌的决定。”BrianGoetz感谢你指出这一点。我没有想到“函数”的主要目的不是返回一个值,而是进行一些重要的更改,但这也会返回某种状态,或用于链接的对象,或其他东西。也许将来的某种语言会对这些类型的函数进行区分。(Ada的初步版本区分了不允许产生副作用的“功能”和“值返回程序”。这并没有纳入1983年的最终标准。)