Kotlin星投影逆变型

时间:2018-01-09 16:10:53

标签: java generics kotlin

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

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

4 个答案:

答案 0 :(得分:3)

Kotlin documentation中也解释了这一点:

  

对于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)
    }
}

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

fun <F> usingFoo(con: Foo<F>, t: F) {
    con.accept(t)
}

(使用F以区分类类型参数T

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

现在,您的引言只是说使用con: Foo<*>类型而不是con: Foo<T>的参数会产生以下影响:“您无法调用任何具有{{1}的方法在签名“

因此,以下失败:

T

使用fun <F> usingFoo(con: Foo<*>, t: F) { con.accept(t) //not allowed!! } 的实例调用accept是不安全的,因为我们不知道F的类型(由明星投影表示)。

  

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

答案 1 :(得分:1)

进一步由用户s1m回答。

class Foo<in T> {
    fun accept(t: T) {
        println(t)
    }

    fun doSomething(): String {
        return "yay"
    }
}

fun <F> usingFoo(con: Foo<*>, t: F) {
    //  con.accept(t) //not allowed!!
    val result = con.doSomething() // OK!
}

fun main() {
    val number: Int = 5
    usingFoo(Foo<Number>(), number)
} 

关于使用

Foo<*> 

以一种安全的方式,doSomething()方法显示了一个示例(禁止的方法是使用T类型的方法)。

答案 2 :(得分:1)

让我们了解为什么逆变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
}

<强>为什么使用*,而不是指定一个类型吗

我们希望保持类型未知,所以我们不会最终使用 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
}

使用站点

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
}

协变与上限

声明站点

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

使用站点

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
}

反变而不下界

声明站点

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

使用站点

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
}

反变与下界

下界不科特林支持。因此,在上面的Consumer类,我们不能有像in Pet : T(下限),如我们有out T : Pet(上限)。我们知道,消费者可以消费T,它的亚型。 Nothing 是 Kotlin 中所有类型的子类型,就像 Any? 是所有类型的超类型一样。并且因为,在T是在星形投影未知的,唯一已知的亚型TNothing。这就是为什么消费者的明星投影只能消耗Nothing。因此,说Consumer<*>是一样的东西的话Consumer<in Nothing>


就是这样!希望帮助清理的东西了。

答案 3 :(得分:0)

要添加到sm1 ..的绝妙答案,以下是协变类型的示例:

   class Foo<out U: Number> {
        private var u: U? = null
            
        fun produce(): U? {
            return u
        }
    }
    
    fun usingFoo(foo: Foo<*>) {
        foo.produce()    
    }
    
    fun main() {    
        usingFoo(Foo<Number>()) //OK
        usingFoo(Foo<Any>()) // Not OK
        
//Direct calls (not using method with *)    
    Foo<Number>().produce() //OK
    Foo<Any>().produce() //Not OK  
       
    }

没有星型投影的直接调用中的行为相同,但*基本上表示:我们定义了泛型(Foo),并且在方法使用时(usingFoo),我们仍然不知道具体类型。保留超级类型(在本例中为Number)的原始约束。 *只是说:具体类型在其他地方定义。