Reputation: 343
I'm fairly when it comes to Java, and I've decided to dig a little into the implementations and uses of the API, especially the Stream API.
I've made an implementation after thinking I got it right, and it worked. However I realized something that bugged me out.
The mapMulti
function takes in parameter a BiConsumer
:
default <R> Stream<R> mapMulti(BiConsumer<? super T, ? super Consumer<R>> mapper) {
Objects.requireNonNull(mapper);
return flatMap(e -> {
SpinedBuffer<R> buffer = new SpinedBuffer<>();
mapper.accept(e, buffer);
return StreamSupport.stream(buffer.spliterator(), false);
});
}
I wanted to benchmark the mapMulti function by passing it the accept function of my Element class (that's why I discard the value of s
), and the ExecutionPlan
simply has values to benchmark with JMH.
public void mapMultiTest(ExecutionPlan exec){
Stream<Integer> s = exec.elts.stream().mapMulti(Element::accept);
}
Here is the Element class, which simply decomposes an int into prime factors, and calls forEach on the consumer.
public record Element(int value) {
public void accept(Consumer<Integer> consumer) {
decomp().forEach(consumer);
}
public ArrayList<Integer> decomp() {
ArrayList<Integer> list = new ArrayList<>();
int value = this.value;
while (!isPrime(value)) {
int prime = 2;
while (!isPrime(prime) || value % prime != 0)
prime++;
list.add(prime);
value /= prime;
}
list.add(value);
return list;
}
private boolean isPrime(int num) {
if (num <= 1) {
return false;
}
for (int i = 2; i <= Math.sqrt(num); i++) {
if (num % i == 0) {
return false;
}
}
return true;
}
}
Why is my Element::accept
(which is theorically the mapper
arg) considered as valid when it is not of the type BiConsumer
, and takes only one argument, even though when it is called inside mapMulti
, it takes the element and buffer argument.
I may totally be missing something obvious or having a wrong understanding of those kind of functions, but I'm having some troubles understanding BiConsumer
, Consumer
, Functions
, BiFunctions
, etc.
Upvotes: 1
Views: 288
Reputation: 21055
Since Element.accept
is an instance (i.e. non-static) method, the first argument of the functional call will be the Element
instance.
Specifically, the method reference Element::accept
corresponds to a BiConsumer.accept(T t, U u)
method with the first argument being an Element
instance and the second argument being the sole argument of Element.accept
(a Consumer<Integer>
instance).
The following statements are therefore effectively equivalent:
myStream.mapMulti(Element::accept);
and
myStream.mapMulti(
(Element element, Consumer<Integer> consumer) -> element.accept(consumer)
);
and
BiConsumer<Element, Consumer<Integer>> accept = new BiConsumer<>() {
@Override
public void accept(Element t, Consumer<Integer> u) {
t.accept(u);
}
};
To summarize the (rather technical) Java Language Specification section 15.13.1. Compile-Time Declaration of a Method Reference, a method reference of the form ClassName::methodName
used as a function type with n arguments a1
, a2
, ..., an
will reference either of the following (whichever one exists):
ClassName.methodName(a1, a2, ..., an)
(the matching static method with parameter types matching all n arguments). The resulting function type will use the arguments matching the argument types of the ClassName.methodName
method.ClassName.methodName(a2, ..., an)
(the matching non-static method with parameter types matching the last n-1 arguments). The resulting function type will use an instance of ClassName
as the first argument, with the remaining arguments matching the argument types of the ClassName.methodName
method.The following concrete example shows creating a BiConsumer
from a static method and from a non-static method:
public class MyExample {
private static class MyType {
public void nonStaticMethod(Consumer<Integer> consumer) {}
public static void staticMethod(MyType t, Consumer<Integer> consumer) {}
}
public static void main(String[] args) {
// Equivalent to (t, consumer) -> MyType.staticMethod(t, consumer)
BiConsumer<MyType, Consumer<Integer>> c1 = MyType::staticMethod;
// Equivalent to (t, consumer) -> t.nonStaticMethod(consumer);
BiConsumer<MyType, Consumer<Integer>> c2 = MyType::nonStaticMethod;
}
}
Upvotes: 0
Reputation: 343
So, as @Thomas Kläger pointed out in the comments:
Element.accept()
is an instance method. To call it, you need two objects: anElement
instance and aConsumer<Integer> consumer
. Method references are smart enough to detect this as aBiConsumer<Element, Consumer<Integer> consumer
So
elts.stream().<Integer>mapMulti((elt,cons)->{
elt.accept(cons);
})
and
elts.stream().<Integer>mapMulti(Element::accept)
are the same thing.
Upvotes: 2