javalambdamethod-reference

Function::apply when BiFunction<T, U, R> is required


I am confused about the following line:

Seq<String> s1 = seq.zip(split, Function::apply);

In this snippet:

static String underscoreToCamel(String str) {
        UnaryOperator<String> capitalize = s -> s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase();
        
        Seq<UnaryOperator<String>> seq = c -> {
            c.accept(String::toLowerCase);
            while (true) {
                c.accept(capitalize);
            }
        };
        List<String> split = Arrays.asList(str.split("_"));
        Seq<String> s1 = seq.zip(split, Function::apply);
        String a = s1.join("");
        return a;
    }

public interface Seq<T> {
        void consume(Consumer<T> consumer);

        static <T> Seq<T> unit(T t) {
            return consumer -> consumer.accept(t);
        }

        default <E> Seq<E> map(Function<T, E> function) {
            return consumer -> consume(t -> consumer.accept(function.apply(t)));
        }

        default <E> Seq<E> flatMap(Function<T, Seq<E>> function) {
            return consumer -> consume(t -> function.apply(t).consume(consumer));
        }


        default String join(String sep) {
            StringJoiner joiner = new StringJoiner(sep);
            consume(t -> joiner.add(t.toString()));
            return joiner.toString();
        }

        static <T> T stop() {
            throw StopException.INSTANCE;
        }

        default void consumeTillStop(Consumer<T> consumer) {
            try {
                consume(consumer);
            } catch (StopException ignore) {}
        }

        default <U, R> Seq<R> zip(Iterable<U> iterable, BiFunction<T, U, R> function) {
            return c -> {
                Iterator<U> iterator = iterable.iterator();
                consumeTillStop(t -> {
                    if (iterator.hasNext()) {
                        c.accept(function.apply(t, iterator.next()));
                    } else {
                        stop();
                    }
                });
            };
        }

    }

I do understand that Function::apply is a method reference and that the method wants a BiFunction<T, U, R>. But I do not get how this is compatible.

What exactly does it resolve to? Why can I supply Function::apply in this case?


Solution

  • Non-static method reference

    This is indeed an interesting one, as it uses one of the special rules that method references allow for.

    Quick example, suppose you demand a Function<String, Integer>, then one could write String::length, eventhough this method has a different signature. The requested signature is:

    Integer apply(String x) { ... }
    

    but we supplied a method that only has int length(), so no String argument at all. However, that method is non-static and operates on instances of String. So Java can assume you meant to call that method on that instance, and by that deduce the first parameter to the method. I.e. s -> s.length().


    Generic details

    The same is happening in your setup. First of all, you have to understand what the generics for your requested BiFunction<T, U, R> resolve to. They are:

    Quite complex, but okay.

    Actual signature

    Now, the requested signature is:

    Consumer<UnaryOperator<String>> apply(UnaryOperator<String> t, String u)
    

    When you give your method reference Function::apply, whose signature is:

    R apply(T t)
    

    so, in this case:

    Consumer<UnaryOperator<String>> apply(String u)
    

    Then, the first argument from the required signature (UnaryOperator<String> t) is again deducted from the fact that Function::apply is a non-static method that operates on the Function instance, which happens to be compatible with UnaryOperator<String>, since UnaryOperator extends Function.

    Implementation

    So Function::apply essentially is the same as implementing your BiFunction as:

    Consumer<UnaryOperator<String>> apply(UnaryOperator<String> t, String u) {
      return t.apply(u);
    }
    

    Taking the function from the first argument and applying it to the second, returning the result.