As known, when doing the accumulation, "reduce" always returns a new immutable object while "collect" will make changes on a mutable object.
However when I accidentally assign one method reference to both reduce and collect method, it compiles without any errors. Why?
Take a look at the following code:
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);
}
}
Basically, the question reduces to this: BiConsumer
is a functional interface whose function is declared like this:
void accept(T t, U u)
You've given it a method reference with the correct parameters, but the wrong return type:
public MutableContainer reduce(long param) {
return new MutableContainer(param);
}
[The T
parameter is actually the this
object when reduce
is called, since reduce
is an instance method and not a static method. That's why the parameters are correct.] The return type is MutableContainer
and not void
, however. So the question is, why does the compiler accept it?
Intuitively, I think this is because a method reference is, more or less, equivalent to an anonymous class that looks like this:
new BiConsumer<MutableContainer,Long>() {
@Override
public void accept(MutableContainer t, Long u) {
t.reduce(u);
}
}
Note that t.reduce(u)
will return a result. However, the result is discarded. Since it's OK to call a method with a result and discard the result, I think that, by extension, that's why it's OK to use a method reference where the method returns the result, for a functional interface whose method returns void
.
Legalistically, I believe the reason is in JLS 15.12.2.5. This section is difficult and I don't fully understand it, but somewhere in this section it says
If e is an exact method reference expression ... R2 is void.
where, if I read it correctly, R2 is the result type of a functional interface method. I think this is the clause that allows a non-void method reference to be used where a void method reference is expected.
(Edit: As Ismail pointed out in the comments, JLS 15.13.2 may be the correct clause here; it talks about a method reference being congruent with a function type, and one of the conditions for this is that the result of the function type is void
.)
Anyway, that should hopefully explain why it compiles. Of course, the compiler can't always tell when you're doing something that will produce incorrect results.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With