要让一个接受 Function 的方法也能安全接收 Function(其中 B extends A),需将参数声明为 Function super B, String>,这正是 PECS 原则在逆变场景下的典型应用,从而实现类型安全的协变兼容。
先说结论:在 Java 泛型的世界里,Function 和 Function 之间并不是父子关系——哪怕 B 确实继承了 A,这两个函数类型也是不变的(invariant)。这意味着你无法直接把 Function 传给一个要求 Function 的变量——编译器会直接拒绝,因为类型不安全。
问题的本质在于:你真正想要的是把一个 B 的实例传入函数,而这个函数需要能够处理 B 或者 B 的任意父类型(比如 A)。此时就该让逆变(contravariance)出场了:使用 ? super B 来表示“这个函数能接受 B 或者 B 的任意超类”。简单来说,就是声明为 Function super B, String>。
那么,具体如何实现?看一段实际代码就清楚了:
// 接口定义
public interface Displayable {}
public interface Purchasable extends Displayable {}
// 构造器或方法签名(关键!)
public class DisplayablePanel {
private final Function super Purchasable, String> displayTextFn;
public DisplayablePanel(Function super Purchasable, String> fn) {
this.displayTextFn = fn;
}
public void render(Purchasable item) {
String label = displayTextFn.apply(item); // ✅ 安全调用
// ... 使用 label 创建 UI 元素
}
}
这样设计之后,下面几种函数都能顺利传入——宽松程度各不相同,但全部合法:
// ✅ 接受 Purchasable(最具体) FunctionpurchasableFn = p -> p.getName() + " ($" + p.getPrice() + ")"; // ✅ 接受 Displayable(更宽泛,仍兼容) Function displayableFn = d -> d.toString(); // ✅ 甚至接受 Object(极端宽泛,但合法) Function
有几个要点值得特别提醒:
- ❌ 千万别用 Function extends A, String>——方向完全反了。? extends A 表示“返回 A 的子类型”,但 apply() 方法需要的是输入参数。对输入来说,? extends A 是只读不安全的,无法保证你传入的 B 能被这个函数接受。
- ✅ ? super B 遵循的是经典的 PECS 原则(Producer Extends, Consumer Super)。注意,Function 的第一个类型参数是消费者——它消费输入对象,所以用 super 才对。
- 这个方案完全不会破坏类型安全。道理很简单:B 是 A 的子类型,任何能处理 B 的函数自然能处理 B 的实例;而 ? super B 允许函数处理 B、A 甚至 Object 这些更宽泛的类型,恰好满足“所有 Purchasable 都是 Displayable”这个设计意图。
一句话总结:当你需要函数参数支持子类型的实参时,不要试图把函数本身向上转型——那条路不通。正确的做法是通过 ? super SubType 来重新定义函数类型边界。这才是 Java 泛型中实现既安全又灵活的接口适配的正确方式。
