您现在的位置是:首页 >学无止境 >Kotlin泛型<in, out, where>概念及示例网站首页学无止境

Kotlin泛型<in, out, where>概念及示例

Calvin880828 2024-06-19 13:56:30
简介Kotlin泛型<in, out, where>概念及示例

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关键字声明,表明它仅用作输入类型。StringConsumerAnyConsumer是两个实现了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关键字声明,表明它仅用作输出类型。StringProducerAnyProducer是两个实现了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被定义为泛型类型,仅限于同时是NumberComparable<T>的类型。这意味着只能使用类型为NumberComparable<T>的参数调用比较函数。

val myClass = MyClass<Int>()
val result = myClass.compare(1,2)

在这个例子中,我们可以看到该类只接受Int 类型,因为它是一个Number并且是可比较的。

通过使用in和out,Kotlin提供了对声明点变型的支持,这使我们能够在声明点定义泛型类型的子类型关系,而不是在使用点定义。这使我们能够更安全、更简洁地使用更多的泛型类型,并防止某些可能出现的类型错误。

结论

协变关系

以下是在Kotlin中使用in和out能够实现的一些功能,如果没有它们,这些功能将会很困难或不可能实现:

  1. 定义协变和逆变的泛型类型:out允许我们定义协变的泛型类型,这意味着子类型关系得到保留(例如,List<Child>List<Parent>的子类型)。另一方面,in允许我们定义逆变的泛型类型,这意味着子类型关系的方向被颠倒(例如,Comparator<Parent>Comparator<Child>的子类型)。
  2. 在函数参数和返回类型中使用泛型类型:使用in和out允许我们在函数参数和返回类型中使用泛型类型,以保留子类型关系。例如,我们可以定义一个以List<out Parent>作为参数的函数,这意味着它可以接受List<Child>List<Parent>,但不能接受List<Grandparent>。类似地,我们可以定义一个返回Comparator<in Child>的函数,这意味着它可以返回Comparator<Child>Comparator<Parent>,但不能返回Comparator<Grandparent>
  3. 避免强制转换和类型检查:使用in和out可以在某些情况下避免强制转换和类型检查,因为编译器可以推断不同泛型类型之间的子类型关系。例如,如果我们有一个List<out Any>,我们可以安全地将列表的元素访问为Any,因为我们知道列表的所有元素至少是Any类型的。

总之,in和out是Kotlin中强大的工具,没有它们,我们将不得不使用强制转换、类型检查和其他解决方案来实现相同水平的表达能力和安全性。

参考

【Kotlin泛型】http://www.enmalvi.com/2021/01/31/kotlin-28/

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。