Java 8 BiFunction, BiConsumer and BiPredicate Tutorial
Introduction
This tutorial explains the three two-argument functional interfaces in Java 8'sjava.util.function package: BiFunction, BiConsumer and BiPredicate. We will start with how they relate to the one-argument interfaces you probably know - Function, Consumer and Predicate. We will then look at each one's definition and default methods, run one example that uses all three (including method references and a constructor reference), and finish with where the JDK itself uses them and what to do when you need three arguments.(If you are new to functional interfaces, start with the java.util.function package tutorialClick to read: java.util.function package tutorial and the FunctionClick to read: Function, ConsumerClick to read: Consumer and PredicateClick to read: Predicate tutorials.)
The one-argument interfaces and their Bi- twins
Function, Consumer and Predicate each take one argument. Many real operations need two: a salary and a raise percentage, a map key and its value, an employee and a threshold. For these, the package has a Bi- version of each interface that takes two arguments of possibly different types,T and U.
BiFunction<T, U, R>
Definition ofBiFunction in java.util.function is -R apply(T t, U u)
AND
default <V> BiFunction<T,U,V> andThen(Function<? super R,? extends V> after)apply()takes aTand aUand returns anR. Its function descriptorClick to read: function descriptor is (T, U) -> R.andThen()returns a new BiFunction that passes the result to a one-argumentFunction. There is nocompose(), because a function that runs before would have to produce two values.
BiConsumer<T, U>
Definition ofBiConsumer in java.util.function is -void accept(T t, U u)
AND
default BiConsumer<T,U> andThen(BiConsumer<? super T,? super U> after)accept()takes two arguments and returns nothing; it exists for its side effect. Descriptor: (T, U) -> void.andThen()returns a BiConsumer that runs this one and thenafter, both with the same two arguments.
BiPredicate<T, U>
Definition ofBiPredicate in java.util.function is -boolean test(T t, U u)
AND
default BiPredicate<T,U> and(...), or(...), negate()test()checks a condition on two arguments. Descriptor: (T, U) -> boolean.and(),or()andnegate()combine BiPredicates exactly like their Predicate counterparts;and()andor()short-circuit.
Java 8 code example of BiFunction, BiConsumer and BiPredicate
The example uses a smallEmployee class with a name and a monthly salary.
Employee.java
//Employee.java
public class Employee {
private final String name;
private final double salary;
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
public String getName() { return name; }
public double getSalary() { return salary; }
@Override
public String toString() { return name + " (" + salary + ")"; }
}
Java 8 code showing BiFunction, BiConsumer and BiPredicate with andThen, and, negate and method references
//BiInterfaces.java
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Function;
public class BiInterfaces {
public static void main(String[] args) {
// 1. BiFunction<T, U, R>: two inputs, one result
BiFunction<Employee, Double, Double> raised =
(e, percent) -> e.getSalary() * (1 + percent / 100);
Employee tom = new Employee("Tom Jones", 5000);
System.out.println("1. Tom after a 4% raise: " + raised.apply(tom, 4.0));
// 2. andThen(Function): pass the BiFunction's result to a Function
Function<Double, String> asText = amount -> String.format("%.2f a month", amount);
System.out.println("2. " + raised.andThen(asText).apply(tom, 4.0));
// 3. A constructor reference as a BiFunction
BiFunction<String, Double, Employee> hire = Employee::new;
Employee nancy = hire.apply("Nancy Smith", 5800.0);
System.out.println("3. hired: " + nancy);
// 4. BiConsumer<T, U>: two inputs, no result - Map.forEach takes one
Map<String, Integer> leaveLeft = new TreeMap<>();
leaveLeft.put("Tom Jones", 12);
leaveLeft.put("Harry Major", 3);
BiConsumer<String, Integer> show =
(name, days) -> System.out.print(name + ": " + days + " ");
BiConsumer<String, Integer> warn = (name, days) -> {
if (days < 5) System.out.print("(low!) ");
};
System.out.print("4. ");
leaveLeft.forEach(show.andThen(warn));
System.out.println();
// 5. BiPredicate<T, U>: a test on two inputs
BiPredicate<Employee, Double> earnsAtLeast = (e, amount) -> e.getSalary() >= amount;
BiPredicate<Employee, Double> underDouble = (e, amount) -> e.getSalary() <= amount * 2;
BiPredicate<Employee, Double> inBand = earnsAtLeast.and(underDouble);
System.out.println("5. Nancy at least 5000? " + earnsAtLeast.test(nancy, 5000.0)
+ ", in band 3000-6000? " + inBand.test(nancy, 3000.0)
+ ", not at least 6000? " + earnsAtLeast.negate().test(nancy, 6000.0));
// 6. Method references that match BiPredicate and BiFunction
BiPredicate<String, String> startsWith = String::startsWith;
BiFunction<Integer, Integer, Integer> larger = Math::max;
System.out.println("6. \"Harry Major\" starts with \"Har\"? "
+ startsWith.test("Harry Major", "Har")
+ ", larger of 7 and 12: " + larger.apply(7, 12));
// 7. Passing a BiFunction into a method
List<Employee> team = Arrays.asList(tom, nancy, new Employee("Harry Major", 4100));
double bonuses = payAll(team, 10.0, (e, pct) -> e.getSalary() * pct / 100);
System.out.println("7. total bonuses at 10%: " + bonuses);
}
// Applies any two-argument rule to every employee
static double payAll(List<Employee> team, double rate,
BiFunction<Employee, Double, Double> rule) {
double total = 0;
for (Employee e : team) {
total += rule.apply(e, rate);
}
return total;
}
}
1. Tom after a 4% raise: 5200.0 2. 5200.00 a month 3. hired: Nancy Smith (5800.0) 4. Harry Major: 3 (low!) Tom Jones: 12 5. Nancy at least 5000? true, in band 3000-6000? true, not at least 6000? true 6. "Harry Major" starts with "Har"? true, larger of 7 and 12: 12 7. total bonuses at 10%: 1490.0
Explanation of the code
- Part 1:
raisedtakes anEmployeeand a percentage and returns the new salary. The types of its two inputs differ, which is exactly whatBiFunction<T, U, R>allows. - Part 2:
andThen(asText)builds a new BiFunction that formats the raised salary as text. - Part 3: the two-argument constructor
Employee(String, double)matchesBiFunction<String, Double, Employee>, soEmployee::newcan be used as one - see constructor referencesClick to read: constructor references. - Part 4:
Map.forEach()takes a BiConsumer of key and value.show.andThen(warn)prints each entry and then a warning for low balances. - Part 5:
earnsAtLeast.and(underDouble)tests both conditions with the same employee and amount, andnegate()inverts a test. - Part 6: method referencesClick to read: method references fit the Bi- interfaces too.
String::startsWithis an instance method called on the first argument with the second as its parameter, so it is aBiPredicate<String, String>;Math::maxis a static method with two parameters. - Part 7:
payAll()accepts any two-argument rule, so the bonus calculation is passed in as a lambda - the behaviour is a parameter, like the strategies in the Strategy patternClick to read: Strategy pattern.
Where the JDK uses them
- BiFunction:
Map.compute(),computeIfPresent(),merge()andreplaceAll()(see Map merge, putIfAbsent and forEachClick to read tutorial: Java 8 Map merge, putIfAbsent and forEach with Examples),Stream.reduce()'s three-argument form, andCompletableFuture.thenCombine()andhandle()(see combining CompletableFuturesClick to read tutorial: Java 8 CompletableFuture: Combining and Error Handling). - BiConsumer:
Map.forEach(), the accumulator of a CollectorClick to read tutorial: Java 8 Custom Collector with Collector.of() Tutorial, andCompletableFuture.whenComplete(). - BiPredicate:
Files.find(), which tests each path together with its file attributes. - BinaryOperator<T>, a BiFunction whose two inputs and result all have the same type, is so common that it has its own interface - see UnaryOperator and BinaryOperatorClick to read tutorial: Java 8 UnaryOperator and BinaryOperator Tutorial.
What about three arguments?
Java 8 stops at two: there is noTriFunction. If you need more inputs, either group them into one object - often the clearer design - or declare your own interface, annotated with @FunctionalInterface -
A custom three-argument functional interface
@FunctionalInterface
interface TriFunction<A, B, C, R> {
R apply(A a, B b, C c);
}
TriFunction<Double, Double, Integer, Double> compound =
(amount, rate, years) -> amount * Math.pow(1 + rate, years);
Summary
In this tutorial we first saw that BiFunction, BiConsumer and BiPredicate are the two-argument versions of Function, Consumer and Predicate. Next we looked at each interface's abstract method, function descriptor and default methods:andThen() for BiFunction and BiConsumer, and and(), or() and negate() for BiPredicate. We then ran an example covering all three, including a constructor reference and method references that match them. Lastly, we saw where the JDK uses these interfaces and how to handle three or more arguments.Java 8 Tutorials
Java 8 - How to Sort a Map by Key or ValueClick to read tutorial: Java 8 - How to Sort a Map by Key or Value Java 8 UnaryOperator and BinaryOperator TutorialClick to read tutorial: Java 8 UnaryOperator and BinaryOperator Tutorial Java 8 CompletableFuture Tutorial with ExamplesClick to read tutorial: Java 8 CompletableFuture Tutorial with Examples Java 8 CompletableFuture: Combining and Error HandlingClick to read tutorial: Java 8 CompletableFuture: Combining and Error Handling Java 8 Parallel Streams Tutorial with ExamplesClick to read tutorial: Java 8 Parallel Streams Tutorial with Examples Java 8 Collectors.toMap Tutorial with ExamplesClick to read tutorial: Java 8 Collectors.toMap Tutorial with Examples Java 8 Custom Collector with Collector.of() TutorialClick to read tutorial: Java 8 Custom Collector with Collector.of() Tutorial
Java 8 - How to Sort a Map by Key or ValueClick to read tutorial: Java 8 - How to Sort a Map by Key or Value Java 8 UnaryOperator and BinaryOperator TutorialClick to read tutorial: Java 8 UnaryOperator and BinaryOperator Tutorial Java 8 CompletableFuture Tutorial with ExamplesClick to read tutorial: Java 8 CompletableFuture Tutorial with Examples Java 8 CompletableFuture: Combining and Error HandlingClick to read tutorial: Java 8 CompletableFuture: Combining and Error Handling Java 8 Parallel Streams Tutorial with ExamplesClick to read tutorial: Java 8 Parallel Streams Tutorial with Examples Java 8 Collectors.toMap Tutorial with ExamplesClick to read tutorial: Java 8 Collectors.toMap Tutorial with Examples Java 8 Custom Collector with Collector.of() TutorialClick to read tutorial: Java 8 Custom Collector with Collector.of() Tutorial