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?
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()
.
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:
T
: UnaryOperator<String>
U
: String
R
: Consumer<T>
, so Consumer<UnaryOperator<String>>
Quite complex, but okay.
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
.
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.