8

Edit: My question here was answered. To summarize, I was confused about the usage of non-static method references. There the functional interface and referenced method have a different number of parameters.

What answered my question is the comment and the accepted answer.


I am currently reading the Java Tutorial about Stream reduction methods (https://docs.oracle.com/javase/tutorial/collections/streams/reduction.html). There I found a piece of code that I thought was wrong, so I made a simpler code to make sure.

// B.java file
import java.util.*;

public class B 
{
  public static void main(String[] args)
  {
    List<Integer> zahlen = new LinkedList<Integer>();
    zahlen.add(1);
    zahlen.add(2);
    zahlen.add(3);
    Averager averageCollect = zahlen.stream()
      .collect(Averager::new, Averager::addcount, Averager::combine);
    System.out.println(averageCollect.average());
  }
}

// Averager.java from the official Java tutorial
public class Averager
{
    private int total = 0;
    private int count = 0;

    public double average() {
        return count > 0 ? ((double) total)/count : 0;
    }

    public void addcount(int i) { total += i; count++;}
    public void combine(Averager other) {
        total += other.total;
        count += other.count;
    }
}

The reason I thought this wouldn't work is because of the line:

Averager averageCollect = zahlen.stream()
  .collect(Averager::new, Averager::addcount, Averager::combine);

In the Java documentation for the Stream.collect (https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#collect-java.util.function.Supplier-java.util.function.BiConsumer-java.util.function.BiConsumer-) it says that as the second parameter a function which matches the functional interface BiConsumer is required which has an abstract method with two arguments. But Averager.addcount and Averager.combine only have one parameter.

I also checked with lambda expressions:

Averager averageCollect = zahlen.stream()
  .collect(Averager::new, (a,b) -> a.addcount(b), (a,b) -> a.combine(b));

This code also works and as the second and third parameter I have functions with two parameters.

Why exactly does the code I wrote above work, even though functions with only one parameter were given? And why are there error messages when I change both Averager.addcount and Averager.combine to have two parameters like this?

public void addcount(Averager one, Integer i)
public void combine(Averager one, Averager other)

If I do that I get the following error message:

B.java:12: error: no suitable method found for collect(Averager::new,Averager::addcount,Averager::combine)
      .collect(Averager::new, Averager::addcount, Averager::combine);
      ^
    method Stream.collect(Supplier,BiConsumer,BiConsumer) is not applicable
      (cannot infer type-variable(s) R#1
        (argument mismatch; invalid method reference
          cannot find symbol
            symbol:   method addcount(R#1,Integer)
            location: class Averager))
    method Stream.collect(Collector) is not applicable
      (cannot infer type-variable(s) R#2,A
        (actual and formal argument lists differ in length))
  where R#1,T,R#2,A are type-variables:
    R#1 extends Object declared in method collect(Supplier,BiConsumer,BiConsumer)
    T extends Object declared in interface Stream
    R#2 extends Object declared in method collect(Collector)
    A extends Object declared in method collect(Collector)
1 error

Please help me understand.

2
  • 3
    the current item can be the first parameter. So a BiConsumer can me method(a, b) for a static method or a.method(b) for an instance method. In your broken example; you have passed an instance method. Commented Jan 24, 2016 at 23:43
  • 2
    See also Oracle’s tutorial “Method References”, esp. the section “Kinds of Method References”. Commented Jan 25, 2016 at 9:54

1 Answer 1

6
Averager averageCollect = zahlen.stream()
  .collect(Averager::new, Averager::addcount, Averager::combine);

This is fine. It is equivalent to

Averager averageCollect = zahlen.stream()
  .collect(() -> new Averager(),
           (myAverager, n) -> myAverager.addcount(n),
           (dst, src) -> dst.combine(src))

Remember every nonstatic method has a hidden this parameter. In this case it is (correctly) binding this to the first argument of the accumulator and combiner callbacks.

It will also work with static methods such as:

public static void addcount(Averager a, int i) {
    a.total += i;
    a.count++;
}
public static void combine(Averager dst, Averager src) {
    dst.total += src.total;
    dst.count += src.count;
}

which hopefully makes it clearer what is happening.

But there is no need to change the code.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.