Kotlin 对逆变类型的星形投影

sun*_*r20 -1 java generics types kotlin type-projection

我正在阅读并试图理解 Kotlin 类型的预测,有时我会想出这样令人困惑的事情:

对于逆变类型参数,例如Consumer<in T>,星形投影等效于<in Nothing>。实际上,您不能调用具有T此类星形投影签名的任何方法。 如果类型参数是逆变的,则它仅充当消费者,并且正如我们之前讨论的那样,您并不确切知道它可以消耗什么。因此,你不能给它任何东西来消费。

当有关类型参数的信息不重要时,您可以使用星形投影语法:您不使用任何引用签名中类型参数的方法,或者您只读取数据而不关心其特定类型。例如,您可以将printFirst函数List<*>作为参数来实现。

具有星形投影的逆变类型是什么意思,它是如何实现的

Yog*_*ity 11

让我们了解逆变的星形投影等于 的原因。但在此之前,我们需要了解星形投影到底是什么以及它如何用于协变。Consumer<in T><in Nothing>Producer<out T>


星光投影

T当您使用某个泛型类的星形投影时,您对使用从该泛型类返回或接受作为参数的函数或属性不感兴趣T。例如,以下函数仅返回List两者中较大的一个。size我们只对不返回或接受的属性感兴趣T,它只返回一个Int

例子

fun getBiggerOfTwo(list1: MutableList<*>, list2: MutableList<*>) : MutableList<*> {
    return if (list1.size >= list2.size) list1 else list2
}
Run Code Online (Sandbox Code Playgroud)

为什么使用*而不是指定类型?

我们希望保持类型未知,这样我们就不会 T意外地使用特定的函数。例如,在上面的函数中,如果我们被允许调用list1.add(Something()),我们可能最终会在我们只想比较列表的地方意外地改变列表。因此,当我们调用该函数时,编译器将通过标记错误来帮助我们add()。除了创建安全性之外,我们的函数还将可重用于各种类型,而不仅仅是某些特定类型。


无上限协变

在以下示例中,我们将使用ProducerConsumer类来生成和使用 的各种子类型class Pet(val cutenessIndex: String)

申报现场

class Producer<out T> {
    private val items = listOf<T>()
    fun produce() : T = items.last()
    fun size(): Int = items.size
}
Run Code Online (Sandbox Code Playgroud)

使用现场

fun useProducer(star: Producer<*>) {
    // Produces Any?, a Pet is not guaranteed because T is unknown
    val anyNullable = star.produce()      // Not useful

    // Can't use functions and properties of Pet.
    anyNullable.cutenessIndex             // Error

    // Can use T-independent functions and properties
    star.size()                           // OK
}
Run Code Online (Sandbox Code Playgroud)

与上限协变

申报现场

class Producer<out T : Pet> {
    private val pets = listOf<T>()
    fun produce() : T = pets.last()
}
Run Code Online (Sandbox Code Playgroud)

使用现场

fun useProducer(star: Producer<*>) {
    // Even though we use * here, T is known to be at least a Pet
    // because it's an upper bound at the declaration site.
    // So, Pet is guaranteed.
    val pet = star.produce()              // OK

    // Can use properties and functions of Pet.
    pet.cutenessIndex                     // OK
}
Run Code Online (Sandbox Code Playgroud)

无下界逆变

申报现场

class Consumer<in T> {
    private val items = mutableListOf<T>()
    fun consume(item: T) = items.add(item)
    fun size(): Int = items.size
}
Run Code Online (Sandbox Code Playgroud)

使用现场

fun useConsumer(consumer: Consumer<*>) {
    // Cannot consume anything because 
    // lower bound is not supported in Kotlin and T is unknown.
    consumer.consume(Pet())               // Error

    // Can use T-independent functions and properties.
    consumer.size()                       // OK
}
Run Code Online (Sandbox Code Playgroud)

与下界逆变

Kotlin 不支持下限。因此,在Consumer上面的类中,我们不能有像in Pet : T(上界)那样的out T : Pet(下界)。众所周知,消费者可以消费T及其子类型。Nothing是 Kotlin 中所有类型的子类型,就像Any?是所有类型的超类型一样。由于T在星投影中未知,唯一已知的子类型TNothing。这就是为什么一个消费者的星投影只能消费Nothing。因此,说Consumer<*>与说是同一件事Consumer<in Nothing>


就是这样!希望这有助于解决问题。


s1m*_*nw1 7

这也在 Kotlin文档中进行了解释:

对于Foo<in T>,其中T逆变类型参数,Foo<*>等效于Foo<in Nothing>。这意味着当未知时,您无法以 Foo<*>安全的方式写入任何内容T

例子

我们有一个Foo<T>具有逆变T(声明站点)的类,即Foo仅作为 的消费者工作T

class Foo<in T> {
    fun accept(t: T) {
        println(t)
    }
}
Run Code Online (Sandbox Code Playgroud)

我们可以在简单的泛型函数中使用这种类型,如下所示:

fun <F> usingFoo(con: Foo<F>, t: F) {
    con.accept(t)
}
Run Code Online (Sandbox Code Playgroud)

(用于F区分类类型参数T

这很好用Foo,因为我们按预期使用:作为Ts的消费者。

现在,您的引述只是说使用 typecon: Foo<*> 而不是参数con: Foo<T>会产生以下效果:“您不能调用T签名中具有的任何方法”

因此,以下失败:

fun <F> usingFoo(con: Foo<*>, t: F) {
    con.accept(t) //not allowed!!
}
Run Code Online (Sandbox Code Playgroud)

accept使用 的实例调用是不安全的,F因为我们不知道的类型Foo(由星形投影表示)。

Star Projection:有时你想说你对类型参数一无所知,但仍然想以安全的方式使用它。这里的安全方法是定义泛型类型的这种投影,该泛型类型的每个具体实例都将是该投影的子类型。