5

众所周知,在进行累加时,“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);
    }
}
4

1 回答 1

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。)

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

于 2014-12-28T07:17:42.220 回答