Java 8收集vs reduce

nin*_*ing 5 java reduce collect java-8

众所周知,在进行累积时,"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);
    }
}
Run Code Online (Sandbox Code Playgroud)

ajb*_*ajb 5

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

void accept(T t, U u)
Run Code Online (Sandbox Code Playgroud)

你给它一个带有正确参数的方法引用,但错误的返回类型:

public MutableContainer reduce(long param) {
    return new MutableContainer(param);
}
Run Code Online (Sandbox Code Playgroud)

[该T参数实际上是调用this时的对象reduce,因为reduce它是实例方法而不是静态方法.这就是参数正确的原因.]然而,返回类型是MutableContainer和不是void.所以问题是,为什么编译器会接受它?

直觉上,我认为这是因为方法引用或多或少等同于一个看起来像这样的匿名类:

new BiConsumer<MutableContainer,Long>() {
    @Override
    public void accept(MutableContainer t, Long u) {
         t.reduce(u);
     }
}
Run Code Online (Sandbox Code Playgroud)

请注意,t.reduce(u)将返回结果.但是,结果被丢弃了.因为可以使用结果调用方法并丢弃结果,所以我认为,通过扩展,这就是为什么可以使用方法返回结果的方法引用,对于方法返回的功能接口void.

在法律上,我认为原因在于JLS 15.12.2.5.这部分很难,我不完全理解它,但在本节的某个地方它说

如果e是精确的方法参考表达式...... R 2是无效的.

其中,如果我正确读取,R 2是功能接口方法的结果类型.我认为这是允许在需要void方法引用的地方使用非void方法引用的子句.

(编辑:正如Ismail在评论中指出的那样,JLS 15.13.2可能是这里的正确条款;它讨论的方法引用与函数类型是一致的,其中一个条件是函数类型的结果是void.)

无论如何,这应该有希望解释为什么它编译.当然,编译器无法始终告诉您何时执行会产生错误结果的操作.

  • 你的动机是正确的.就像您可以调用方法并丢弃返回类型一样,您可以将结果承载方法转换为返回空白的函数接口.之前的评论者担心这种灵活性会引发错误,但是替代方案更糟糕 - 你甚至无法将`aList :: add`转换为`Consumer`,因为`add`会返回一些东西 - 这真的很烦人,如果你不能说`x.forEach(list :: add)`!这两种解决方案都会惹恼某人.这种方式被认为是邪恶中的较小者.实际上,它甚至都不是一个接近的电话. (6认同)
  • 我认为[15.13.2](http://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.13.2)在这种情况下更具相关性 - "A方法引用表达式在赋值上下文中与目标类型T [...]兼容,如果[...]函数类型[T]的结果为空" (2认同)