Kotlin泛型<in, out, where>概念及示例
在 Kotlin 中,泛型用于指定类、接口或方法可以操作的对象类型。
in
in
关键字用于指定泛型类型是“输入”类型,这意味着它将仅用作函数或类的参数。
interface ReadOnly {
fun read(): Any
}
class ReadWrite<in T>(private var value: T) : ReadOnly {
override fun read(): Any = value
// 'in' keyword allows to use T as an input only
// so, the following line will give a compile error
// fun write(value: T) { this.value = value }
}
另外一个例子:
interface Consumer<in T> {
fun consume(item: T)
}
class StringConsumer : Consumer<String> {
override fun consume(item: String) {
println("Consuming string: $item")
}
}
class AnyConsumer : Consumer<Any> {
override fun consume(item: Any) {
println("Consuming any type: $item")
}
}
fun main() {
val stringConsumer = StringConsumer()
stringConsumer.consume("Hello") // prints "Consuming string: Hello"
val anyConsumer: Consumer<Any> = AnyConsumer()
anyConsumer.consume("Hello") // prints "Consuming any type: Hello"
anyConsumer.consume(123) // prints "Consuming any type: 123"
}
在上面的例子中,Consumer
是一个接口,只有一个方法 consume
接受一个T类型的参数。类型参数T使用in
关键字声明,表明它仅用作输入类型。StringConsumer
和AnyConsumer
是两个实现了Consumer
接口的类,都可以用来消费各自类型的实例。
out
out
关键字用于指定泛型类型是“输出”类型,这意味着它将仅用作函数或类的返回类型。
interface WriteOnly {
fun write(value: Any)
}
class ReadWrite<out T>(private var value: T) : WriteOnly {
// 'out' keyword allows to use T as an output only
// so, the following line will give a compile error
// fun read(): T = value
override fun write(value: Any) {
// this.value = value
}
}
另一个例子:
interface Producer<out T> {
fun produce(): T
}
class StringProducer : Producer<String> {
override fun produce(): String = "Hello"
}
class AnyProducer : Producer<Any> {
override fun produce(): Any = "Hello"
}
fun main() {
val stringProducer = StringProducer()
println(stringProducer.produce()) // prints "Hello"
val anyProducer: Producer<Any> = AnyProducer()
println(anyProducer.produce()) // prints "Hello"
}
在上面的例子中,Producer
是一个接口,它有一个单一的方法 produce
,它返回一个T
类型的值。类型参数T
使用out
关键字声明,表明它仅用作输出类型。StringProducer
和AnyProducer
是两个实现了Producer
接口的类,都可以用来生成各自类型的实例。
where
where
关键字用于指定对可用作参数或返回类型的类型的约束。
interface Processor<T> where T : CharSequence, T : Comparable<T> {
fun process(value: T): Int
}
class StringProcessor : Processor<String> {
override fun process(value: String): Int = value.length
}
另外一个例子:
interface Processor<T> where T : CharSequence, T : Comparable<T> {
fun process(value: T): Int
}
class StringProcessor : Processor<String> {
override fun process(value: String): Int = value.length
}
fun main() {
val stringProcessor = StringProcessor()
println(stringProcessor.process("Hello")) // prints "5"
}
在上面的例子中,Processor
是一个接口,只有一个方法process
接受一个T
类型的参数并返回一个Int
。类型参数T
使用where
关键字声明,并指定两个约束:T必须实现CharSequence
接口,并且它必须与自身可比较。StringProcessor
是一个实现String
类型的Processor
接口的类,它可以用来处理String
值。
考虑下面类:
class Box<T>(val item: T)
此类定义了一个通用类型T,可用于指定存储在Box中的项目的类型。在没有任何额外约束的情况下,此类可用于创建任何类型的Box:
val intBox = Box(1)
val stringBox = Box("hello")
现在考虑下面的类:
class InOut<in T, out R>(val item: T) {
fun get(): R {
return item as R
}
}
这里,T
被定义为“输入”类型(使用in
关键字),R
被定义为“输出”类型(使用out
关键字)。这意味着T
只能用作函数的参数,而R
只能用作返回类型。这将允许我们定义一个接受InOut
类型并返回内部项目的函数:
fun test(input: InOut<String, Any>): Any {
return input.get()
}
最后考虑下面的类:
class MyClass<T> where T : Number, T : Comparable<T> {
fun compare(item1: T, item2: T): Int {
return item1.compareTo(item2)
}
}
在这里,T
被定义为泛型类型,仅限于同时是Number
和Comparable<T>
的类型。这意味着只能使用类型为Number
和Comparable<T>
的参数调用比较函数。
val myClass = MyClass<Int>()
val result = myClass.compare(1,2)
在这个例子中,我们可以看到该类只接受Int 类型,因为它是一个Number
并且是可比较的。
通过使用in和out,Kotlin提供了对声明点变型的支持,这使我们能够在声明点定义泛型类型的子类型关系,而不是在使用点定义。这使我们能够更安全、更简洁地使用更多的泛型类型,并防止某些可能出现的类型错误。
结论
以下是在Kotlin中使用in和out能够实现的一些功能,如果没有它们,这些功能将会很困难或不可能实现:
- 定义协变和逆变的泛型类型:out允许我们定义协变的泛型类型,这意味着子类型关系得到保留(例如,
List<Child>
是List<Parent>
的子类型)。另一方面,in允许我们定义逆变的泛型类型,这意味着子类型关系的方向被颠倒(例如,Comparator<Parent>
是Comparator<Child>
的子类型)。 - 在函数参数和返回类型中使用泛型类型:使用in和out允许我们在函数参数和返回类型中使用泛型类型,以保留子类型关系。例如,我们可以定义一个以
List<out Parent>
作为参数的函数,这意味着它可以接受List<Child>
或List<Parent>
,但不能接受List<Grandparent>
。类似地,我们可以定义一个返回Comparator<in Child>
的函数,这意味着它可以返回Comparator<Child>
或Comparator<Parent>
,但不能返回Comparator<Grandparent>
。 - 避免强制转换和类型检查:使用in和out可以在某些情况下避免强制转换和类型检查,因为编译器可以推断不同泛型类型之间的子类型关系。例如,如果我们有一个
List<out Any>
,我们可以安全地将列表的元素访问为Any,因为我们知道列表的所有元素至少是Any类型的。
总之,in和out是Kotlin中强大的工具,没有它们,我们将不得不使用强制转换、类型检查和其他解决方案来实现相同水平的表达能力和安全性。
参考
【Kotlin泛型】http://www.enmalvi.com/2021/01/31/kotlin-28/