out
(协变)
out
关键字用于实现泛型的协变。协变意味着如果 B
是 A
的子类型,那么 Producer<B>
可以被视为 Producer<A>
的子类型。这里的 Producer
是一个使用泛型类型参数的类或接口,并且该泛型类型参数被标记为 out
。
kotlin
interface Producer<out T> {
fun produce(): T
}
open class Animal
class Cat : Animal()
fun main() {
val catProducer: Producer<Cat> = object : Producer<Cat> {
override fun produce(): Cat = Cat()
}
// 协变,允许这样的赋值。
val animalProducer: Producer<Animal> = catProducer
}
在这个例子中,由于 Producer
接口的泛型参数 T
被标记为 out
,所以 Producer<Cat>
类型的对象可以赋值给 Producer<Animal>
类型的变量,因为 Cat
是 Animal
的子类型。
in
(逆变)
in
关键字用于实现泛型的逆变。逆变表示如果 B
是 A
的子类型,那么 Consumer<A>
可以被视为 Consumer<B>
的子类型。这里的 Consumer
是一个使用泛型类型参数的类或接口,并且该泛型类型参数被标记为 in
。
kotlin
interface Consumer<in T> {
fun consume(item: T)
}
open class Animal
class Cat : Animal()
fun main() {
val animalConsumer: Consumer<Animal> = object : Consumer<Animal> {
override fun consume(item: Animal) {
}
}
// 逆变,允许这样的赋值。
val catConsumer: Consumer<Cat> = animalConsumer
}
在这个例子中,因为 Consumer
接口的泛型参数 T
被标记为 in
,所以 Consumer<Animal>
类型的对象可以赋值给 Consumer<Cat>
类型的变量,尽管 Cat
是 Animal
的子类型。
where
(泛型约束)
where
关键字用于为泛型类型参数添加额外的约束条件。它允许你指定泛型类型参数必须满足的多个条件。
kotlin
class Container<T> where T : Number, T : Comparable<T> {
private val items = mutableListOf<T>()
fun add(item: T) {
items.add(item)
}
fun getMax(): T? {
return items.maxByOrNull { it }
}
}
fun main() {
val container = Container<Int>()
container.add(10)
container.add(20)
println(container.getMax())
}
在这个例子中,Container
类的泛型类型参数 T
受到 where
子句的约束,要求 T
必须是 Number
类型,同时还必须实现 Comparable<T>
接口。这样,Container
类内部就可以安全地使用 Number
相关的操作以及 Comparable
接口提供的比较功能。
声明处型变:out
(协变)
当一个类型参数 T
被声明为 out
时,意味着该类型参数只能在类或接口的成员中作为返回类型出现,而不能作为参数类型。这样可以确保该类或接口是 T
的生产者,而非消费者。
kotlin
interface Source<out T> {
fun nextT(): T
}
val stringSource: Source<String> = object : Source<String> {
override fun nextT(): String = "Hello"
}
// 由于协变,这里赋值是合法的。
val anySource: Source<Any> = stringSource
在这个例子中,Source
接口的类型参数 T
被声明为 out
,所以 Source<String>
可以安全地赋值给 Source<Any>
,因为 Source
只生产 T
类型的对象,不会消费它们。
声明处型变:in
(逆变)
当一个类型参数 T
被声明为 in
时,意味着该类型参数只能在类或接口的成员中作为参数类型出现,而不能作为返回类型。这样可以确保该类或接口是 T
的消费者,而非生产者。
kotlin
interface Comparable<in T> {
operator fun compareTo(other: T): Int
}
val numberComparable: Comparable<Number> = object : Comparable<Number> {
override fun compareTo(other: Number): Int = this.toDouble().compareTo(other.toDouble())
}
// 由于逆变,这里赋值是合法的。
val doubleComparable: Comparable<Double> = numberComparable
在这个例子中,Comparable
接口的类型参数 T
被声明为 in
,所以 Comparable<Number>
可以安全地赋值给 Comparable<Double>
,因为 Comparable
只消费 T
类型的对象,不会生产它们。
使用处型变:类型投影
将类型参数 T
声明为 out
并避免在使用处出现子类型问题是很容易的,但有些类实际上不能仅限于只返回 T
类型的值。Array
就是一个很好的例子:
kotlin
class Array<T>(val size: Int) {
operator fun get(index: Int): T { ... }
operator fun set(index: Int, value: T) { ... }
}
这个类在 T
上既不能是协变的,也不能是逆变的。这就带来了一定的局限性。考虑以下函数:
kotlin
fun copy(from: Array<Any>, to: Array<Any>) {
assert(from.size == to.size)
for (i in from.indices)
to[i] = from[i]
}
这个函数的作用是将元素从一个数组复制到另一个数组。让我们在实际中尝试使用它:
kotlin
val ints: Array<Int> = arrayOf(1, 2, 3)
val anys = Array<Any>(3) { "" }
// ints 类型是 Array<Int>,但期望的是 Array<Any>。
copy(ints, anys)
在这里,你又遇到了熟悉的问题:Array<T>
在 T
上是不变的,所以 Array<Int>
和 Array<Any>
都不是对方的子类型。为什么呢?这是因为 copy
函数可能会有意外的行为,例如,它可能会尝试向 from
数组中写入一个 String
,而如果你实际传入的是一个 Int
数组,稍后就会抛出 ClassCastException
异常。
为了禁止 copy
函数向 from
数组写入数据,你可以这样做:
kotlin
fun copy(from: Array<out Any>, to: Array<Any>) { ... }
这就是类型投影,意味着 from
不是一个普通的数组,而是一个受限(投影)的数组。你只能调用那些返回类型参数 T
的方法,在这种情况下,意味着你只能调用 get()
方法。这就是我们实现使用处型变的方式,它对应于 Java
的 Array<? extends Object>
,只是稍微简单一些。
你也可以使用 in
来进行类型投影:
kotlin
fun fill(dest: Array<in String>, value: String) { ... }
Array<in String>
对应于 Java
的 Array<? super String>
。这意味着你可以将一个 String
数组、CharSequence
数组或 Object
数组传递给 fill()
函数。
kotlin
fun copy(from: Array<out Any>, to: Array<in Any>) {
assert(from.size == to.size)
for (i in from.indices)
to[i] = from[i]
}
fun main() {
val ints: Array<Int> = arrayOf(1, 2, 3)
val any = Array<Any>(3) { "" }
copy(ints, any)
}
使用处型变:*
投影
有时候,你对类型参数一无所知,但仍然希望以安全的方式使用它。这里的安全方式是定义泛型类型的这样一种投影,使得该泛型类型的每个具体实例化都是该投影的子类型。
Kotlin
为此提供了所谓的 *
投影语法:
-
对于
Foo<out T : TUpper>
,其中T
是具有上界TUpper
的协变类型参数,Foo<*>
等价于Foo<out TUpper>
。这意味着当T
未知时,你可以安全地从Foo<*>
中读取TUpper
类型的值。 -
对于
Foo<in T>
,其中T
是逆变类型参数,Foo<*>
等价于Foo<in Nothing>
。这意味着当T
未知时,你无法以安全的方式向Foo<*>
中写入数据。 -
对于
Foo<T : TUpper>
,其中T
是具有上界TUpper
的不变类型参数,Foo<*>
在读取值时等价于Foo<out TUpper>
,在写入值时等价于Foo<in Nothing>
。
如果一个泛型类型有多个类型参数,每个参数都可以独立进行投影。例如,如果类型被声明为 interface Function<in T, out U>
,你可以使用以下 *
投影:
-
Function<*, String>
表示Function<in Nothing, String>
。 -
Function<Int, *>
表示Function<Int, out Any?>
。 -
Function<*, *>
表示Function<in Nothing, out Any?>
。
*
投影非常类似于 Java
的原始类型,但更安全。
kotlin
// 定义一个协变的泛型类。
class Box<out T : Number>(val value: T)
fun main() {
// 创建一个包含 Int 类型的 Box。
val intBox: Box<Int> = Box(10)
// 使用 * 投影。
val starBox: Box<*> = intBox
// 可以安全地读取 Number 类型的值。
val number: Number = starBox.value
println(number)
}
在这个例子中,Box<out T : Number>
是一个协变的泛型类。Box<*>
等价于 Box<out Number>
,因此可以安全地从 starBox
中读取 Number
类型的值。
kotlin
// 定义一个逆变的泛型类。
class Printer<in T> {
fun print(value: T) {
println(value)
}
}
fun main() {
// 创建一个 Printer<Int> 实例。
val intPrinter: Printer<Int> = Printer()
// 使用 * 投影。
val starPrinter: Printer<*> = intPrinter
// 无法安全地向 starPrinter 中写入值。
// 这行代码会编译错误。
// starPrinter.print(10)
}
在这个例子中,Printer<in T>
是一个逆变的泛型类。Printer<*>
等价于 Printer<in Nothing>
,因此无法安全地向 starPrinter
中写入任何值。
kotlin
// 定义一个不变的泛型类。
class Container<T : CharSequence>(var value: T)
fun main() {
// 创建一个包含 String 类型的 Container。
val stringContainer: Container<String> = Container("Hello")
// 使用 * 投影。
val starContainer: Container<*> = stringContainer
// 可以安全地读取 CharSequence 类型的值。
val charSequence: CharSequence = starContainer.value
println(charSequence)
// 无法安全地向 starContainer 中写入值。
// 这行代码会编译错误。
// starContainer.value = "World"
}
在这个例子中,Container<T : CharSequence>
是一个不变的泛型类。Container<*>
在读取值时等价于 Container<out CharSequence>
,在写入值时等价于 Container<in Nothing>
,因此可以安全地读取 CharSequence
类型的值,但无法安全地写入值。
kotlin
// 定义一个带有两个类型参数的泛型接口。
interface Function<in T, out U> {
fun apply(arg: T): U
}
fun main() {
// 定义一个实现 Function<Int, String> 的匿名类。
val intToStringFunction: Function<Int, String> = object : Function<Int, String> {
override fun apply(arg: Int): String {
return arg.toString()
}
}
// 使用 * 投影。
val function1: Function<*, String> = intToStringFunction
// Function<*, String> 等价于 Function<in Nothing, String>,
// 无法调用 apply 方法。
// 这行代码会编译错误。
// function1.apply(10)
// 使用 * 投影。
val function2: Function<Int, *> = intToStringFunction
// Function<Int, *> 等价于 Function<Int, out Any?>,
// 可以调用 apply 方法。
val result: Any? = function2.apply(10)
println(result)
// 使用 * 投影。
val function3: Function<*, *> = intToStringFunction
// Function<*, *> 等价于 Function<in Nothing, out Any?>。
// 无法调用 apply 方法。
// 这行代码会编译错误。
// function3.apply(10)
}
在这个例子中,Function<in T, out U>
是一个带有两个类型参数的泛型接口。通过不同的 *
投影方式,可以安全地处理不同的使用场景。