BiConsumer

Java 中的 PECS 原则在 BiConsumer super K, ? super V> 遍历 Map 时,直接体现为“消费者用 super”——因为这个函数式接口是来消费键值对的,不是生产它们。
为什么 BiConsumer 是消费者?
BiConsumer super K, ? super V> 的作用是接收一对数据(K 和 V),执行某个操作(比如打印、存库、累加),它不返回任何结果,也不向外提供 K 或 V 的实例。它的角色是“消耗”键值,而非“产出”键值。所以按 PECS 原则,两个类型参数都应使用 ? super。
-
? super K:允许传入比 K 更通用的类型(如K=String时,Object或CharSequence都可接受); -
? super V:同理,让消费逻辑能兼容 V 及其父类,提升复用性。
Map.forEach 的签名设计正基于此
Map 这个方法声明,明确告诉调用者:你传进来的 BiConsumer 必须能处理 K 类型或更宽泛的类型,以及 V 类型或更宽泛的类型。
- 例如:
Map,你可以安全传入map = ...; BiConsumer,因为Object是String的父类,Number是Integer的父类; - 但不能传
BiConsumer,因为Long不是Integer的父类,违反? super V约束。
对比 Producer 场景,凸显 super 的必要性
如果换成只读场景(比如 Stream 提取 key),你会看到 map.entrySet().stream().map(Map.Entry::getKey) 返回的是 Stream ——这里 K 是被“产出”的,所以若想泛化,就该用 Stream extends K>(Producer Extends)。而 forEach 没有返回值,纯消费,自然选 super。
- 写法上:Producer →
? extends T(安全读);Consumer →? super T(安全写/接受); - forEach 不读取返回值,只把 K/V “喂给” action,action 就是消费者,必须能接住它们——所以用
super才够宽、够安全。
实际编码中的好处
这种设计让通用处理逻辑可以复用。比如写一个统计所有 Map 值总和的方法:
public static long sumValues(Map, ? extends Number> map) { // 注意:这是 Producer 场景,读 value → ? extends Number
return map.values().stream()
.mapToLong(Number::longValue)
.sum();
}
而如果你要统一记录日志,不管 Map 具体是什么键值类型:
BiConsumer<object object> logger = (k, v) -> System.out.println("Key: " + k + ", Value: " + v);
myStringIntMap.forEach(logger); // ✅ 编译通过,因为 Object 是 String 和 Integer 的公共父类</object>
这正是 ? super K 和 ? super V 提供的灵活性来源。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











