Is there any way to use BiConsumers as simply as Consumers?

3.4k views Asked by At

This is just a theorical question with no concrete application.

I have the following method which I will not touch. It could (if possible at all) be used as a BiConsumer.

void doSmallThing(A a, B b) {
  // do something with a and b.
}

void doBigThing(List<A> as, B b) {
  // What to do?
}

How can I iterate on as while keeping b constant and use this::doSmallThing in doBigThing?

Of course the following doesn't work.

void doBigThing(List<A> as, B b) {
  as.stream()
  .forEach(this::doSmallThing);
}

The following works nice and is actually what I use everyday.

void doBigThing(List<A> as, B b) {
  as.stream()
  .forEach(a -> doSmallThing(a, b));
}

The following also works well, but is a bit more tricky.

Consumer<A> doSmallThingWithFixedB(B b) {
  return (a) -> doSmallThing(a, b);
}

void doBigThing(List<A> as, B b) {
  as.stream()
  .forEach(doSmallThingWithFixedB(b))
}

But all of those solutions don't get the simplicity of the Consumer case. So is there anything simple that exists for BiConsumer?

3

There are 3 answers

2
Tagir Valeev On BEST ANSWER

You want to "bind" the function argument. Unfortunately there's no built-in mechanism to do this in Java 8 (except binding the object for instance methods like this::). You may generalize your doSmallThingWithFixedB method like this:

public class Bind {
    public static <A, B> Consumer<A> bindLast(BiConsumer<A, B> fn, B b) {
        return a -> fn.accept(a, b);
    }

    public static <A, B> Consumer<B> bindFirst(BiConsumer<A, B> fn, A a) {
        return b -> fn.accept(a, b);
    }
}

And use:

void doBigThing(List<A> as, B b) {
  as.stream()
    .forEach(Bind.bindLast(this::doSmallThing, b));
}

Probably there's some third-party library which already contains such methods. However using explicit lambda seems ok for me. You should not try to express everything with method references.

2
JB Nizet On

BiConsumers are used when iterating over Map entries for example:

Map<A, B> map = ...;
map.forEach(this::doSomething);

Stream.collect() also takes BiConsumers as arguments, but it's used less often than an iteration on map entries.

2
Christoffer Hammarström On

Add a method doSmallThingto B:

class B {
    public void doSmallThing(A a) {
         YourClass.doSmallThing(a, this); // You may want to inline this.
    }
}

and call it from doBigThing:

void doBigThing(List<A> as, B b) {
    as.stream()
        .forEach(b::doSmallThing);
}