Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Method reference does not fulfil the functional interface contract but it compile. How it is possible?

Tags:

In the class below, I pass the method reference WordCounterEx::accumulate as second parameter to the reduce method. The signature of reduce method is:

<U> U reduce(U identity,
             BiFunction<U, ? super T, U> accumulator,
             BinaryOperator<U> combiner);

Thus the second parameter of reduce method, must fulfil the BiFunction recipe. But the passed accumulate method is not BiFunction (it has only one parameter). Why it still compile?

public class WordCounterEx {
    private final int counter;
    private final boolean lastSpace;

    public WordCounterEx(int counter, boolean lastSpace) {
        this.counter = counter;
        this.lastSpace = lastSpace;
    }

    public int countWords(Stream<Character> stream) {
        WordCounterEx wordCounter = stream.reduce(new WordCounterEx(0, true),
                //HOW CAN THIS WORK? here must come BiFunction - R apply(T t, U u);
                WordCounterEx::accumulate,
                WordCounterEx::combine);
        return wordCounter.counter;
    }

    public WordCounterEx accumulate(Character c) {
        if(Character.isWhitespace(c)) {
            return lastSpace ?
                    this :
                    new WordCounterEx(counter, true);
        } else {
            return lastSpace ?
                    new WordCounterEx(counter+1, false) :
                    this;
        }
    }

    public WordCounterEx combine(WordCounterEx wordCounter) {
        return new WordCounterEx(counter + wordCounter.counter
                ,wordCounter.lastSpace /*does not matter*/);
    }
}
like image 545
kuceraf Avatar asked May 01 '17 10:05

kuceraf


2 Answers

accumulate() is an instance method, and you refer to it by class name and method name (not by instance and method name). So if I wanted to call the method you are giving me, I would usually do myEx.accumulate(myCh). Thus I provide two things, the WordCounterEx instance and the character. Therefore, used this way the method counts as a BiFunction<WordCounterEx, ? super Character, WordCounterEx>.

If instead you had given me for example this::accumulate, the object to call the method on would have been given (this), and it could no longer be used as a BiFunction (in my Eclipse I get “The method reduce(U, BiFunction, BinaryOperator) in the type Stream is not applicable for the arguments (WordCounterEx, this::accumulate, WordCounterEx::combine)”).

like image 135
Ole V.V. Avatar answered Oct 11 '22 11:10

Ole V.V.


The WordCounterEx#countWords method can be rewritten as follows:

public int countWordsWithInstance(Stream<Character> stream) {
    WordCounterEx wordCounter = stream.reduce(new WordCounterEx(0, true),
            this::accumulate,
            WordCounterEx::combine);
    return wordCounter.counter;
}

public WordCounterEx accumulate(WordCounterEx wc,Character c) {
    if(Character.isWhitespace(c)) {
        return wc.lastSpace ?
                wc :
                new WordCounterEx(wc.counter, true);
    } else {
        return wc.lastSpace ?
                new WordCounterEx(wc.counter+1, false) :
                wc;
    }
}

In this case the accumulate method must have WordCounterEx wc in its signature

like image 20
kuceraf Avatar answered Oct 11 '22 11:10

kuceraf