在Java开发中,经常会遇到多个方法执行的操作逻辑完全一致,仅输入参数的类型存在差异的情况,比如对不同实体类执行相同的校验、日志记录或者数据转换操作。如果直接为每个类型编写独立的方法,会导致大量重复代码,后续修改逻辑时也需要同步调整多个位置,很容易出现遗漏。BiConsumer接口作为Java函数式编程的重要组成部分,能够很好地解决这类问题,通过抽象公共操作逻辑,适配不同的输入类型,实现代码的复用。
BiConsumer接口基础介绍
BiConsumer是java.util.function包下的函数式接口,核心方法是void accept(T t, U u),它接收两个泛型参数,执行对应的操作但不返回任何结果。该接口还提供了一个默认方法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) {
Objects.requireNonNull(after);
return (l, r) -> {
accept(l, r);
after.accept(l, r);
};
}
}
传统重复代码的问题
假设我们有两个实体类User和Order,都需要执行相同的操作:打印两个参数的信息,同时将第一个参数存储到日志集合中。传统写法会为两个类分别编写方法:
import java.util.ArrayList;
import java.util.List;
class User {
private String name;
private int age;
// 构造方法、getter、setter省略
}
class Order {
private String orderId;
private double amount;
// 构造方法、getter、setter省略
}
public class TraditionalDemo {
private static List<User> userLogList = new ArrayList<>();
private static List<Order> orderLogList = new ArrayList<>();
// 处理User类型的方法
public static void handleUser(User user, String extraInfo) {
System.out.println("User name: " + user.getName() + ", extra info: " + extraInfo);
userLogList.add(user);
}
// 处理Order类型的方法
public static void handleOrder(Order order, String extraInfo) {
System.out.println("Order id: " + order.getOrderId() + ", extra info: " + extraInfo);
orderLogList.add(order);
}
public static void main(String[] args) {
User user = new User("张三", 20);
Order order = new Order("O001", 199.9);
handleUser(user, "新增用户");
handleOrder(order, "新增订单");
}
}
上述代码中,handleUser和handleOrder的核心操作逻辑完全一致,仅处理的参数类型不同,随着实体类数量增加,这类重复方法会越来越多,维护成本也会不断升高。
使用BiConsumer重构的步骤
1. 提取公共操作逻辑
首先分析两个方法的公共逻辑:打印信息、将第一个参数加入对应的日志集合。我们可以把日志集合的操作和打印操作抽象成BiConsumer的实现。
2. 定义适配不同输入类型的BiConsumer
通过泛型定义BiConsumer,适配不同的参数类型,同时复用公共的打印逻辑:
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
public class RefactorDemo {
private static List<User> userLogList = new ArrayList<>();
private static List<Order> orderLogList = new ArrayList<>();
// 公共的打印操作逻辑,接收任意类型的第一个参数和字符串第二个参数
private static <T> BiConsumer<T, String> getCommonHandler(BiConsumer<T, String> logAction) {
// 先执行日志存储操作,再执行打印操作
return logAction.andThen((t, extraInfo) -> System.out.println("处理完成,额外信息: " + extraInfo));
}
public static void main(String[] args) {
User user = new User("张三", 20);
Order order = new Order("O001", 199.9);
// 定义User类型的处理逻辑
BiConsumer<User, String> userHandler = (u, extraInfo) -> {
System.out.println("User name: " + u.getName() + ", age: " + u.getAge());
userLogList.add(u);
};
// 定义Order类型的处理逻辑
BiConsumer<Order, String> orderHandler = (o, extraInfo) -> {
System.out.println("Order id: " + o.getOrderId() + ", amount: " + o.getAmount());
orderLogList.add(o);
};
// 包装处理逻辑,加入公共操作
BiConsumer<User, String> wrappedUserHandler = getCommonHandler(userHandler);
BiConsumer<Order, String> wrappedOrderHandler = getCommonHandler(orderHandler);
// 执行操作
wrappedUserHandler.accept(user, "新增用户");
wrappedOrderHandler.accept(order, "新增订单");
System.out.println("用户日志数量: " + userLogList.size());
System.out.println("订单日志数量: " + orderLogList.size());
}
}
3. 进一步优化通用处理方法
如果公共操作中的打印逻辑也是通用的,可以进一步把打印逻辑也抽象出来,减少重复代码:
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
public class AdvancedRefactorDemo {
private static List<User> userLogList = new ArrayList<>();
private static List<Order> orderLogList = new ArrayList<>();
// 通用的处理模板,接收打印逻辑和日志存储逻辑
private static <T> BiConsumer<T, String> createHandler(BiConsumer<T, String> printAction, BiConsumer<T, String> logAction) {
return printAction.andThen(logAction).andThen((t, extraInfo) ->
System.out.println("操作结束,额外信息: " + extraInfo)
);
}
public static void main(String[] args) {
User user = new User("张三", 20);
Order order = new Order("O001", 199.9);
// 创建User处理器
BiConsumer<User, String> userPrint = (u, extraInfo) ->
System.out.println("User name: " + u.getName() + ", age: " + u.getAge() + ", extra: " + extraInfo);
BiConsumer<User, String> userLog = (u, extraInfo) -> userLogList.add(u);
BiConsumer<User, String> userHandler = createHandler(userPrint, userLog);
// 创建Order处理器
BiConsumer<Order, String> orderPrint = (o, extraInfo) ->
System.out.println("Order id: " + o.getOrderId() + ", amount: " + o.getAmount() + ", extra: " + extraInfo);
BiConsumer<Order, String> orderLog = (o, extraInfo) -> orderLogList.add(o);
BiConsumer<Order, String> orderHandler = createHandler(orderPrint, orderLog);
userHandler.accept(user, "新增用户");
orderHandler.accept(order, "新增订单");
}
}
重构注意事项
- BiConsumer的两个参数类型需要明确,避免泛型使用不当导致类型转换错误。
- 如果操作逻辑需要返回值,不能使用BiConsumer,应该选择BiFunction接口。
- 当组合多个BiConsumer时,注意
andThen方法的执行顺序,先执行当前实例的accept方法,再执行传入的after的accept方法。 - 如果公共逻辑中需要抛出异常,需要在BiConsumer的实现中处理,因为函数式接口的抽象方法不允许抛出受检异常,除非在方法内捕获。
适用场景总结
BiConsumer适合重构的场景包括:多个方法接收两个参数且无返回值,操作逻辑高度相似仅参数类型或细节处理不同;需要对不同类型的输入执行相同的附加操作,比如统一的日志、校验、监控等。通过BiConsumer抽象后,代码复用性更高,后续修改公共逻辑只需要调整一处,降低了维护成本。
BiConsumer函数式接口方法重构Javalambda表达式修改时间:2026-06-22 04:18:57