Java BiConsumer Interface
Last modified: April 16, 2025
The java.util.function.BiConsumer
interface represents an operation
that accepts two input arguments and returns no result. It is a functional
interface with a single abstract method accept
. BiConsumer is used
for operations that need to process two values without returning anything.
BiConsumer
is part of Java's functional programming utilities added
in Java 8. It enables side-effect operations on two input parameters. Common
uses include iterating through maps or performing operations on pairs of values.
BiConsumer Interface Overview
BiConsumer
interface contains one abstract method and one default
method. The key method accept
performs the operation on the inputs.
The andThen
method enables chaining multiple BiConsumers.
@FunctionalInterface public interface BiConsumer<T, U> { void accept(T t, U u); default BiConsumer<T, U> andThen(BiConsumer<? super T, ? super U> after); }
The code above shows the structure of BiConsumer
interface. It uses
generics where T and U are input types. The interface is annotated with
@FunctionalInterface to indicate its single abstract method nature.
Basic BiConsumer Usage
The simplest way to use BiConsumer is with lambda expressions. We define how to process two input values in the accept method. The example prints key-value pairs.
package com.zetcode; import java.util.function.BiConsumer; public class Main { public static void main(String[] args) { // Define a BiConsumer that prints two values BiConsumer<String, Integer> printPair = (key, value) -> System.out.println("Key: " + key + ", Value: " + value); // Use the BiConsumer printPair.accept("age", 30); printPair.accept("score", 95); // BiConsumer using method reference BiConsumer<String, String> concatPrinter = System.out::println; concatPrinter.accept("Hello", "World"); } }
This example demonstrates basic BiConsumer usage with lambda and method reference. The printPair takes String and Integer inputs and prints them. Method reference provides concise syntax for existing methods that match BiConsumer signature.
BiConsumer with Map Iteration
BiConsumer
is commonly used with Map's forEach method. The Map's
key-value pairs perfectly match BiConsumer's two input parameters. This enables
clean iteration over map entries.
package com.zetcode; import java.util.HashMap; import java.util.Map; import java.util.function.BiConsumer; public class Main { public static void main(String[] args) { Map<String, Integer> scores = new HashMap<>(); scores.put("Alice", 85); scores.put("Bob", 92); scores.put("Charlie", 78); // BiConsumer to print map entries BiConsumer<String, Integer> printEntry = (name, score) -> System.out.println(name + ": " + score); // Iterate map with BiConsumer scores.forEach(printEntry); // Direct lambda in forEach scores.forEach((k, v) -> { if (v > 80) { System.out.println(k + " passed"); } }); } }
This example shows BiConsumer usage with Map.forEach. We first define a separate BiConsumer for printing entries, then use direct lambda for conditional logic. Map iteration becomes very expressive with BiConsumer.
Chaining BiConsumers with andThen
The andThen
method allows chaining multiple BiConsumers to perform
sequential operations. Each BiConsumer in the chain receives the same input
parameters.
package com.zetcode; import java.util.function.BiConsumer; public class Main { public static void main(String[] args) { // First BiConsumer logs the operation BiConsumer<String, Integer> logger = (item, qty) -> System.out.println("Processing: " + item + " x" + qty); // Second BiConsumer processes the order BiConsumer<String, Integer> processor = (item, qty) -> System.out.println("Ordered " + qty + " of " + item); // Chain the BiConsumers BiConsumer<String, Integer> orderHandler = logger.andThen(processor); // Use the chained BiConsumer orderHandler.accept("Laptop", 2); orderHandler.accept("Mouse", 5); } }
This example demonstrates BiConsumer chaining with andThen
. The
orderHandler executes both logging and processing for each input pair. Both
BiConsumers receive the same parameters when the chain is executed.
BiConsumer for Object Modification
BiConsumer
can be used to modify object properties based on two
input parameters. This is useful for batch updates or configuration operations.
package com.zetcode; import java.util.function.BiConsumer; class Product { String name; double price; Product(String name, double price) { this.name = name; this.price = price; } @Override public String toString() { return name + ": $" + price; } } public class Main { public static void main(String[] args) { // BiConsumer to update product price with discount BiConsumer<Product, Double> applyDiscount = (product, discount) -> { product.price = product.price * (1 - discount/100); System.out.println("Applied " + discount + "% discount"); }; Product laptop = new Product("Laptop", 999.99); Product phone = new Product("Phone", 699.99); applyDiscount.accept(laptop, 10.0); applyDiscount.accept(phone, 15.0); System.out.println(laptop); System.out.println(phone); } }
This example shows BiConsumer modifying object state. The applyDiscount BiConsumer takes a Product and discount percentage, then updates the product's price. This pattern is useful for applying consistent modifications.
BiConsumer in Stream Operations
BiConsumer
can be used in stream operations that process pairs of
values. While not as common as Function in streams, it's useful for terminal
operations with side effects.
package com.zetcode; import java.util.List; import java.util.function.BiConsumer; public class Main { public static void main(String[] args) { List<String> names = List.of("Alice", "Bob", "Charlie"); List<Integer> scores = List.of(85, 92, 78); // BiConsumer to print name-score pairs BiConsumer<String, Integer> printResult = (name, score) -> System.out.println(name + " scored " + score); // Process parallel lists with BiConsumer if (names.size() == scores.size()) { for (int i = 0; i < names.size(); i++) { printResult.accept(names.get(i), scores.get(i)); } } // More complex BiConsumer BiConsumer<String, Integer> resultAnalyzer = (name, score) -> { String status = score >= 80 ? "Pass" : "Fail"; System.out.println(name + ": " + score + " (" + status + ")"); }; System.out.println("\nAnalysis:"); for (int i = 0; i < names.size(); i++) { resultAnalyzer.accept(names.get(i), scores.get(i)); } } }
This example demonstrates BiConsumer processing parallel lists. We define two BiConsumers - one for simple printing and another for more complex analysis. This pattern is useful when processing related data in separate collections.
Primitive Specializations of BiConsumer
Java provides specialized versions of BiConsumer for primitive types to avoid autoboxing overhead. These include ObjIntConsumer, ObjLongConsumer, and ObjDoubleConsumer.
package com.zetcode; import java.util.function.ObjIntConsumer; import java.util.function.ObjDoubleConsumer; public class Main { public static void main(String[] args) { // ObjIntConsumer example ObjIntConsumer<String> printWithNumber = (s, i) -> System.out.println(s + ": " + i); printWithNumber.accept("Count", 42); // ObjDoubleConsumer example ObjDoubleConsumer<String> temperatureLogger = (location, temp) -> System.out.printf("%s: %.1f°C%n", location, temp); temperatureLogger.accept("New York", 22.5); // Using BiConsumer with boxed primitives BiConsumer<String, Integer> boxedConsumer = (s, i) -> System.out.println(s.repeat(i)); boxedConsumer.accept("Hi ", 3); } }
This example shows primitive specializations of BiConsumer. ObjIntConsumer and ObjDoubleConsumer avoid boxing overhead when working with primitives. The last example shows regular BiConsumer with boxed Integer for comparison.
Combining BiConsumer with Other Functional Interfaces
BiConsumer
can be combined with other functional interfaces to
create more complex operations. This example shows pairing it with Function
for data transformation before consumption.
package com.zetcode; import java.util.function.BiConsumer; import java.util.function.Function; public class Main { public static void main(String[] args) { // Function to calculate area Function<Double, Double> areaCalculator = radius -> Math.PI * radius * radius; // BiConsumer to print formatted results BiConsumer<String, Double> resultPrinter = (label, value) -> System.out.printf("%s: %.2f%n", label, value); // Process radius values double[] radii = {1.0, 2.5, 3.0}; for (double r : radii) { double area = areaCalculator.apply(r); resultPrinter.accept("Radius " + r + " area", area); } // More complex combination BiConsumer<String, Function<Double, Double>> calculator = (name, func) -> { double result = func.apply(10.0); System.out.println(name + " at 10.0: " + result); }; calculator.accept("Square", x -> x * x); calculator.accept("Cube", x -> x * x * x); } }
This example demonstrates combining BiConsumer with Function. We first use them separately for calculation and printing, then create a BiConsumer that accepts a Function as its second parameter. This shows the flexibility of functional interfaces.
Source
Java BiConsumer Interface Documentation
In this article, we've covered the essential methods and features of the Java BiConsumer interface. Understanding these concepts is crucial for functional programming and collection processing in modern Java applications.
Author
List all Java tutorials.