ZetCode

Java BiConsumer 接口

最后修改时间:2025 年 4 月 16 日

java.util.function.BiConsumer 接口表示一个接受两个输入参数且不返回结果的操作。它是一个函数式接口,只有一个抽象方法 accept。 BiConsumer 用于需要处理两个值而不返回任何内容的操作。

BiConsumer 是 Java 8 中新增的 Java 函数式编程工具的一部分。它允许对两个输入参数执行副作用操作。常见用途包括迭代地图或对值对执行操作。

BiConsumer 接口概述

BiConsumer 接口包含一个抽象方法和一个默认方法。 关键方法 accept 对输入执行操作。 andThen 方法允许链接多个 BiConsumer。

@FunctionalInterface
public interface BiConsumer<T, U> {
    void accept(T t, U u);
    
    default BiConsumer<T, U> andThen(BiConsumer<? super T, ? super U> after);
}

上面的代码显示了 BiConsumer 接口的结构。它使用泛型,其中 T 和 U 是输入类型。 接口使用 @FunctionalInterface 注解,以表明其单抽象方法的性质。

BiConsumer 的基本用法

使用 BiConsumer 的最简单方法是使用 lambda 表达式。我们定义如何在 accept 方法中处理两个输入值。 此示例打印键值对。

Main.java
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");
    }
}

此示例演示了使用 lambda 和方法引用的 BiConsumer 基本用法。 printPair 接受 String 和 Integer 输入并打印它们。 方法引用为匹配 BiConsumer 签名的现有方法提供了简洁的语法。

BiConsumer 与 Map 迭代

BiConsumer 常用于 Map 的 forEach 方法。 Map 的键值对与 BiConsumer 的两个输入参数完全匹配。 这使得可以干净地迭代 map 条目。

Main.java
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");
            }
        });
    }
}

此示例显示了 BiConsumer 与 Map.forEach 的用法。 我们首先定义一个单独的 BiConsumer 用于打印条目,然后使用直接 lambda 进行条件逻辑。 使用 BiConsumer,Map 迭代变得非常有表现力。

使用 andThen 链接 BiConsumer

andThen 方法允许链接多个 BiConsumer 以执行顺序操作。链中的每个 BiConsumer 接收相同的输入参数。

Main.java
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);
    }
}

此示例演示了使用 andThen 链接 BiConsumer。 orderHandler 为每个输入对执行日志记录和处理。 链执行时,两个 BiConsumer 接收相同的参数。

BiConsumer 用于对象修改

BiConsumer 可用于根据两个输入参数修改对象属性。 这对于批量更新或配置操作很有用。

Main.java
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);
    }
}

此示例显示了 BiConsumer 修改对象状态。 applyDiscount BiConsumer 接受一个 Product 和折扣百分比,然后更新产品的价格。 这种模式对于应用一致的修改很有用。

BiConsumer 在流操作中

BiConsumer 可用于处理值对的流操作。虽然在流中不如 Function 常见,但它对于具有副作用的终端操作很有用。

Main.java
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));
        }
    }
}

此示例演示了 BiConsumer 处理并行列表。我们定义了两个 BiConsumer - 一个用于简单的打印,另一个用于更复杂的分析。当在单独的集合中处理相关数据时,这种模式很有用。

BiConsumer 的基本类型特化

Java 为基本类型提供了 BiConsumer 的专门版本,以避免自动装箱开销。 这些包括 ObjIntConsumer、ObjLongConsumer 和 ObjDoubleConsumer。

Main.java
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);
    }
}

此示例显示了 BiConsumer 的基本类型特化。 ObjIntConsumer 和 ObjDoubleConsumer 在使用基本类型时避免了装箱开销。 最后一个示例显示了带有装箱 Integer 的常规 BiConsumer 用于比较。

将 BiConsumer 与其他函数式接口结合使用

BiConsumer 可以与其他函数式接口结合使用以创建更复杂的操作。此示例展示了将其与 Function 配对以在消耗之前进行数据转换。

Main.java
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);
    }
}

此示例演示了将 BiConsumer 与 Function 结合使用。 我们首先将它们分开用于计算和打印,然后创建一个 BiConsumer,它接受一个 Function 作为其第二个参数。 这显示了函数式接口的灵活性。

来源

Java BiConsumer 接口文档

在本文中,我们介绍了 Java BiConsumer 接口的基本方法和特性。 理解这些概念对于现代 Java 应用程序中的函数式编程和集合处理至关重要。

作者

我叫 Jan Bodnar,是一位经验丰富的程序员,在这一领域拥有多年经验。 我从 2007 年开始撰写编程文章,此后撰写了 1,400 多篇文章和 8 本电子书。 凭借超过 8 年的教学经验,我致力于分享我的知识并帮助他人掌握编程概念。

列出所有Java教程