在编程中,泛型的协变(covariance)和逆变(contravariance)是处理类型兼容性和安全性的重要工具。Kotlin 提供 out
和 in
关键字,而 Java 使用 ? extends
和 ? super
通配符实现类似功能。
本文将从概念、规则、使用场景及示例等方面,详细分析 Kotlin 和 Java 的实现方式及异同点。
协变允许使用子类型的泛型对象赋值给父类型的泛型变量,解决“生产者”场景的问题。
out
? extends
逆变允许使用父类型的泛型对象赋值给子类型的泛型变量,解决“消费者”场景的问题。
in
? super
默认情况下,泛型是不可变的,这意味着 List
不能赋值给 List
,即使 String
是 Object
的子类型。
out
vs ? extends
特性 | Kotlin out |
Java ? extends |
---|---|---|
定义方向 | 子类型 -> 父类型 | 子类型 -> 父类型 |
适用场景 | “生产者”模式(只输出) | “生产者”模式(只输出) |
限制 | 无法用于输入 | 无法用于输入 |
关键字位置 | 声明时(定义站点) | 使用时(使用站点) |
out
class Box(val value: T) // T 是协变的
fun demoOut() {
val stringBox: Box = Box("Hello")
val charSeqBox: Box = stringBox // 合法
println(charSeqBox.value) // 输出:Hello
}
? extends
class Box {
private T value;
public Box(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
void demoExtends() {
Box extends CharSequence> charSeqBox = new Box<>("Hello"); // 合法
CharSequence value = charSeqBox.getValue(); // 合法
System.out.println(value); // 输出:Hello
}
协变的核心限制是:泛型类型不能用于输入。
class Box(private val value: T) {
// fun setValue(newValue: T) {} // 错误,out 泛型不能用于输入
}
fun demoOutWrite() {
val charSeqBox: Box = Box("Hello")
// charSeqBox.setValue("World") // 错误
}
Java 示例:
void demoExtendsWrite() {
List extends CharSequence> list = new ArrayList<>();
// list.add("Hello"); // 错误:不能向上界通配符添加数据
}
3. 逆变:in
vs ? super
特性 | Kotlin in |
Java ? super |
---|---|---|
定义方向 | 父类型 -> 子类型 | 父类型 -> 子类型 |
适用场景 | “消费者”模式(只输入) | “消费者”模式(只输入) |
限制 | 无法从泛型中读取具体类型 | 无法从泛型中读取具体类型 |
关键字位置 | 声明时(定义站点) | 使用时(使用站点) |
in
class Consumer {
fun consume(item: T) {
println("Consuming: $item")
}
}
fun demoIn() {
val charSeqConsumer: Consumer = Consumer() // 合法
charSeqConsumer.consume("Hello") // 合法
val stringConsumer: Consumer = Consumer() // 合法
stringConsumer.consume("Hello") // 合法
}
Java 示例:? super
class Consumer {
public void consume(T item) {
System.out.println("Consuming: " + item);
}
}
void demoSuper() {
Consumer super String> consumer = new Consumer(); // 合法
consumer.consume("Hello"); // 合法
}
逆变的核心限制是:泛型类型不能用作输出具体类型。
class Consumer {
// fun get(): T { return ... } // 错误,in 泛型不能用于输出
}
Java 示例:
void demoSuperRead() {
List super String> list = new ArrayList<>();
Object value = list.get(0); // 合法,但只能读取为 Object
}
特性 | Kotlin | Java |
---|---|---|
默认行为 | 泛型是不可变的 | 泛型是不可变的 |
灵活性 | in 和 out 限制在声明时 |
? extends 和 ? super 灵活调整 |
fun demoInvariance() {
val stringList: List = listOf("Hello")
// val charSeqList: List = stringList // 错误,类型不变性
}
Java:
void demoInvariance() {
List stringList = new ArrayList<>();
// List charSeqList = stringList; // 错误,类型不变性
}
in
、out
和 Java 的 ? super
、? extends
都用于解决泛型的协变与逆变问题。特性 | Kotlin | Java |
---|---|---|
关键字位置 | 声明时(in 和 out ) |
使用时(? super 和 ? extends ) |
灵活性 | 更简洁,声明时确定 | 更灵活,使用时调整边界 |
类型推断能力 | 类型推断更强 | 类型推断相对较弱 |
in
和 out
:适合场景较为简单、类型边界在定义时可以确定的情况。? super
和 ? extends
:更灵活,适合需要在使用时动态调整类型边界的场景。