51

I just started learning Java streams and faced a problem. Please take a look at a the following example. This is part of a Node class:

private Map<String, Node> nodes;

public Optional<Node> child(String name) {
    return Optional.<Node>ofNullable(nodes.get(name));
}

private void findChildren(String name, List<Node> result) {
    child(name).ifPresent(result::add);
    nodes.values().stream()
//          .map(Node::findChildren(name, result))
//          .forEach(Node::findChildren(name, result))
            .forEach(node -> node.findChildren(name, result));
}

My intent was to call #findChildren with the name and result parameters on each node in the stream. I tried to use the method references Node::findChildren with no luck. I'd appreciate solutions other the the one with -> operator.

Is it somehow possible to use the method reference together with a parameter? I like the idea of streams and I just want to make the code more readable.

Actually, I think there is a similar question Method references with a parameter which I read but cannot figure out how to use the bind2 method in my code. Is it the only solution?

Community
  • 1
  • 1
matepal297
  • 961
  • 1
  • 11
  • 19
  • 1
    You can only pass metod references where functional interfaces with similar signature required (I.e. it is possible to infer arguments to the lambda) – Alex Salauyou Apr 23 '15 at 22:31
  • forEach() expects a single Node argument and returns void, so only reference to static method accepting single Node, or no-arg method of Node class with void return can be passed there. Solution: create such method by your own. – Alex Salauyou Apr 23 '15 at 22:35
  • https://marcin-chwedczuk.github.io/method-references-in-java-8 are some good examples for people looking into method references with arguments. – anuj pradhan Apr 02 '18 at 02:48

1 Answers1

53

You can’t use method references for this purpose. You have to resort to lambda expressions. The reason why the bind2 method of the linked question doesn’t work is that you are actually trying to bind two parameters to convert a three-arg function into a one-arg function. There is no similarly simple solution as there is no standard functional interface for three-arg consumers.

It would have to look like

interface ThreeConsumer<T, U, V> {
    void accept(T t, U u, V v);
}
public static <T, U, V> Consumer<T> bind2and3(
                        ThreeConsumer<? super T, U, V> c, U arg2, V arg3) {
    return (arg1) -> c.accept(arg1, arg2, arg3);
}

Then .forEach(bind2and3(Node::findChildren, name, result)); could work. But is this really simpler than .forEach(node -> node.findChildren(name, result));?

Holger
  • 285,553
  • 42
  • 434
  • 765
  • Thank you! How is the `Node::findChildren` with 2 parameters converter to a ThreeConsumer which accepts 3 parameters? – matepal297 Apr 24 '15 at 11:12
  • 4
    For non-`static` methods, you have to count the method receiver as a parameter. In your specific case, the consumer is invoked with a `Node` instance as parameter, on which the `findChildren` method will be invoked. Well, this applies if you use `ClassName::methodName` to refer to an instance method. Instead, you could bind a method reference to a particular receiver by saying `object::methodName`, then the method will be invoked on `object`. This happens, e.g. when you say `System.out::println`, where the method `println` will be invoked on the `PrintStream` instance found in `System.out`. – Holger Apr 24 '15 at 11:17
  • 1
    But in your case, you want process a stream of different `Node` instances so leaving the receiver unbound and counting it as a parameter is what you want. See also [here](http://stackoverflow.com/a/29803257/2711488) – Holger Apr 24 '15 at 11:19
  • 4
    Yet another brain-melting hour just to find out that "You can't." – Mihai Morcov Feb 07 '17 at 12:52