
Java 不支持声明点协变(out)和逆变(in)修饰符,仅通过使用点(use-site)的通配符 ? extends T(协变)和 ? super T(逆变)实现类型安全的变型,需在变量、参数或返回值声明处显式指定。
java 不支持声明点协变(out)和逆变(in)修饰符,仅通过使用点(use-site)的通配符 `? extends t`(协变)和 `? super t`(逆变)实现类型安全的变型,需在变量、参数或返回值声明处显式指定。
在 Kotlin 中,interface Box
然而,Java 泛型不提供 out/in 关键字,也不支持在接口或类定义中声明类型参数的变型行为。Java 的泛型是不变(invariant) 的:Box
✅ 正确的 Java 协变用法:? extends T
当一个泛型类型仅用于“产出”(如 getter 返回值),应使用上界通配符:
public interface Box<T extends SomeType> {
T getItem(); // T 出现在返回位置 → 支持协变使用
}
// 使用时声明协变语义:
Box<? extends SomeType> box = new BoxImpl<>(new ConcreteType()); // ✅ 合法
// Box<? extends SomeType> box2 = new BoxImpl<String>(); // 若 String <: SomeType,也合法此时,box.getItem() 的返回类型被推断为 SomeType(而非具体 T),保障了类型安全:你可安全读取,但不可写入(因为编译器无法确定实际 T 是什么)。
立即学习“Java免费学习笔记(深入)”;
✅ 正确的 Java 逆变用法:? super T
当泛型类型仅用于“消耗”(如方法参数),应使用下界通配符:
public interface Consumer<T> {
void accept(T item);
}
// 使用时声明逆变语义:
Consumer<? super Integer> intConsumer = new Consumer<Number>() {
@Override
public void accept(Number item) {
System.out.println("Received: " + item);
}
};
intConsumer.accept(42); // ✅ Integer 可安全传给 Consumer<Number>此时,你可以向 intConsumer 传入 Integer 或其任意子类型(如 Long 若适配),但不能从中“读取”出具体 T 类型(因 ? super Integer 可能是 Object、Number 等)。
⚠️ 注意事项与常见误区
-
接口/类定义中永远不能加 out/in:Java 语法不支持 interface Box
或 class Box ,此类写法会直接编译失败。 -
通配符仅作用于引用类型,不影响实现类:BoxImpl
本身仍保持不变性;变型约束由使用者(变量声明)承担,而非生产者(类定义)。 -
Kotlin 调用 Java 接口时自动推断投影:Kotlin 编译器会将 Box
视为 Box (若仅含 T 返回值)或 Box (若仅含 T 参数),从而无缝桥接。但若 Java 接口同时含 T getItem() 和 void setItem(T t),Kotlin 将视为 Box(星投影),需手动指定 Box 或 Box 来解包。 - PECS 原则牢记于心:Producer-Extends, Consumer-Super —— 这是选择 ? extends 还是 ? super 的黄金法则。
✅ 总结
| 场景 | Kotlin 语法 | Java 等效方式 |
|---|---|---|
| 声明协变接口 | interface Box |
interface Box |
| 声明逆变接口 | interface Sink |
interface Sink |
| 安全读取(只出) | val x: Box |
Box extends SomeType> x |
| 安全写入(只入) | fun process(s: Sink |
void process(Sink super Integer> s) |
理解并熟练运用 ? extends 与 ? super,是 Java 开发者驾驭泛型类型安全的核心能力——它虽不如 Kotlin 的声明点变型直观,却以简洁、明确、可控的方式,在 JVM 类型系统约束下实现了同等的安全抽象。










