13

What is the best way to functionally compose a java Function and a Consumer? For example given some Function<Object, String> f and some Consumer<String> c then doing f.andThen(c) would feel natural, however that is not how the interfaces work.

The two options I see are either replace Consumer<String> c with Function<String, Void> c or change Consumer<String> c to BiConsumer<Function<Object, String>, String> c and do

accept(Function<Object, String> f, Object o) {
    String thing = f.apply(o);
    //do something with thing
}

Is one of these better than the other? Is there a better way?

Andrew Tobilko
  • 48,120
  • 14
  • 91
  • 142
kag0
  • 5,624
  • 7
  • 34
  • 67

3 Answers3

15

Is this what you are trying to do?

Consumer<Object> composed = o -> c.accept(f.apply(o));

If you find yourself faced with this problem a lot, you can make a static method to do this (but is it really worth it?):

static<T,R> Consumer<T> applyAndAccept(Function<? super T,? extends R> f, Consumer<R> c){
    return t -> c.accept(f.apply(t));
}
Misha
  • 27,433
  • 6
  • 62
  • 78
  • 2
    Sometimes it worth: you can use two cool method references instead of single boring lambda. If your method-references are instance bound and instance is stored in non-final variable, then lambda version may look really clumsy. In this context your proposed static method is even better than non-existing default method `andThen(Consumer)`, because the method reference type will be inferred automatically. – Tagir Valeev Sep 18 '15 at 07:58
  • 1
    @TagirValeev I agree, ability to use method references is a big plus. I just don't run into `Function`-`Consumer` situation often enough, but maybe others do. What I do bump into all the time is the need to compose `Function` and `Predicate`. – Misha Sep 18 '15 at 08:16
  • @Misha why don't you run into this situation often enough? Don't you often do: `arr.forEach(func1.andThen(func2).andThen(func3).andThen(consumer))`? – Sarsaparilla Nov 14 '19 at 09:35
3

You can make a copy of the java.util.function.Function interface in your own code. Call it ConsumableFunction and change all default method parameter types and return values from Function to ConsumableFunction. Then add the following default function:

default Consumer<T> atLast(Consumer<R> consumer) {
    Objects.requireNonNull(consumer);
    return (T t) -> consumer.accept(apply(t));
}

Now let all your Function implmementations implement ConsumableFunction instead. Then you can write code like this:

Consumer<A> consumerForA = functionFromAToB
    .andThen(functionFromBToC)
    .andThen(functionFromCToD)
    .atLast(consumerForD);
dwoakee
  • 31
  • 2
0

Isn't this accomplished using map and forEach?

public void test() {
    Function<Object, String> f = (Object t) -> t.toString();
    Consumer<String> c = (String t) -> {
        System.out.println(t);
    };
    List<Object> data = new ArrayList<>();
    data.stream().map(f).forEach(c);
}
OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213
  • 1
    You essentially don't execute anything, because your stream missing the terminal operation. Also stream processing is just an example of using `Consumer`. They can be useful in other places as well. – Tagir Valeev Sep 18 '15 at 08:23
  • @TagirValeev - Interesting - adding `.toArray()` seems to fix that though. – OldCurmudgeon Sep 18 '15 at 08:29
  • 2
    This is an abuse of `peek`. Just do `.map(f).forEach(c)` or `.map(f).forEachOrdered(c)` if `c` is not thread-safe. – Misha Sep 18 '15 at 08:38