Kotlin泛型中“*”和“任何”之间的差异

laawzig2  于 2022-12-27  发布在  Kotlin
关注(0)|答案(4)|浏览(217)

我不确定我是否完全理解SomeGeneric<*>SomeGeneric<Any>之间的区别。我认为*代表任何东西(通配符),而Any代表ALL对象继承的对象。所以看起来它们应该是相同的,但它们是吗?

jmo0nnb3

jmo0nnb31#

把星星投影看作是一种表示不是任何类型而是某种你不知道确切是什么的固定类型的方法可能是有帮助的。
例如,类型MutableList<*>表示 something 的列表(你不知 prop 体是什么)。所以如果你想在这个列表中添加一些东西,你不会成功。它可能是一个String的列表,或者是一个Int的列表,或者其他东西的列表编译器不会我不允许在这个列表中放入任何对象,因为它不能验证这个列表是否接受这种类型的对象。但是,如果你试图从这样的列表中取出一个元素,您肯定会得到一个Any?类型的对象,因为Kotlin中的所有对象都继承自Any

  • 来自下面的asco注解:*

此外,List<*>可以包含任何类型的对象,但只能包含该类型,因此它可以包含字符串(但只能包含字符串),而List<Any>可以包含字符串和整数等,所有这些都在同一个列表中。

lg40wkob

lg40wkob2#

理解星投影(*)的关键是首先正确理解另外两种类型的投影inout,然后星投影就变得不言自明了。

理解问题

假设你有一个泛型类Crate,你打算用它来储存水果,这个类在T中是不变的,这意味着,这个类可以消费和生产T(水果)。换句话说,此类具有将T作为参数的函数(consume),并返回T(produce)。size()函数是T无关的,它既不接受T,也不返回T

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

但是,如果您只想将这个已经存在的类用作生产者(out T)或消费者(in T),或者不想使用T,而只想使用它的与T无关的函数(如size()),而不用担心意外使用不需要的功能,该怎么办?
解决方案是,我们通过在use-site使用变量修饰符outin*来投影类型。* use-site * 简单地表示我们在哪里使用Crate类。

out投影是T的生成器

通过将Crate投影为out,您将告诉编译器:"当我不小心将Crate类用作T的使用者时,因为我只想安全地将该类用作生产者T,所以给我一个错误":

fun useAsProducer(producer: Crate<out Fruit>) {

    // T is known to be out Fruit, so produces Fruit and its subtypes.
    val fruit = producer.produce()           // OK

    // Fruit is guaranteed. Can use functions and properties of Fruit.
    fruit.getColor()                         // OK
    
    // Consumer not allowed because you don't want to accidentally add
    // oranges, if this is a Crate<Apple>
    producer.consume(Orange())               // Error             
}

in投影是T的使用者

通过将Crate投影为in,您将告诉编译器:"当我不小心将Crate类用作T的生成者时,因为我只想安全地将该类用作T的使用者,所以向我显示错误":

fun useAsConsumer(consumer: Crate<in Orange>) {

    // Produces Any?, no guarantee of Orange because this could
    // be a Crate<Fruit> with apples in it.
    val anyNullable = consumer.produce()     // Not useful
    
    // Not safe to call functions of Orange on the produced items.
    anyNullable.getVitaminC()                // Error

    // T is known to be in Orange, so consumes Orange and its subtypes.
    consumer.consume(MandarinOrange())       // OK
}

星形投影不是T的生产者和消费者

通过将Crate投影为*,您将告诉编译器:"当我不小心将Crate类用作T的生产者或消费者时,因为我不想使用消费和生产T的函数或属性,而只想安全地使用与T无关的函数和属性(如size()),会给我一个错误":

fun useAsStar(star: Crate<*>) {

    // T is unknown, so the star produces the default supertype Any?.
    val anyNullable = star.produce()         // Not useful

    // T is unknown, cannot access its properties and functions.
    anyNullable.getColor()                   // Error

    // Cannot consume because you don't know the type of Crate.
    star.consume(Fruit())                    // Error

    // Only use the T-independent functions and properties.
    star.size()                              // OK
}

Any不是投影

当你说Crate<Any>的时候,你并不是在投射,你只是在使用原始不变类Crate<T>,它可以 * 产生 * 也可以 * 消耗 * Any

fun useAsAny(any: Crate<Any>) {

    // T is known to be Any. So, an invariant produces Any.
    val anyNonNull = any.produce()           // OK

    // T is known to be Any. So, an invariant consumes Any.
    any.consume(Fruit())                     // OK

    // Can use the T-independent functions and properties, of course.
    any.size()                               // OK
}

对于Crate<Apple>或任何其他类似的没有变化修饰符inout,或*的类型也是如此,它将消耗和产生该类型(在这种情况下Apple)。这不是一个投影。这解释了SomeGeneric<*>SomeGeneric<Any>之间的区别,你可以并排比较上面的两个代码片段。

申报地生产者的星形投影

到目前为止,我们看到了Crate类的类型投影outin*,它们在声明站点是不变的:Crate<T>。从这里开始,让我们看看星形投影如何处理在声明点已经是inout的类,并使用类型参数边界:

    • 申报地点**
class ProducerCrate<out T : Fruit> {
    private val fruits = listOf<T>()
    fun produce() : T = fruits.last()
}
    • 使用部位**
fun useProducer(star: ProducerCrate<*>) {

    // Even though we project * here, it is known to be at least a Fruit
    // because it's an upper bound at the declaration-site.
    val fruit = star.produce()               // OK

    // Fruit is guaranteed. Can use functions and properties of Fruit.
    fruit.getColor()                         // OK
}

申报地消费者星投影

    • 申报地点**
class ConsumerCrate<in T> {
    private val items = mutableListOf<T>()
    fun consume(item: T) = items.add(item)
    fun size(): Int = items.size
}
    • 使用部位**
fun useConsumer(consumer: ConsumerCrate<*>) {

    // Cannot consume anything, because the lower bound is not supported
    // in Kotlin and T is unknown
    consumer.consume(Orange())               // Error

    // Only useful for T-independent functions and properties.
    consumer.size()                          // OK
}

注意Kotlin不支持下界,所以在上面的ConsumerCrate类中,我们不能像out T : Orange(上界)那样有in T super Orange(下界)。

声明位置不变式的星形投影

    • 申报地点**
class ProducerConsumerCrate<T : Fruit> {
    private val fruits = mutableListOf<T>()
    fun produce(): T = fruits.last()
    fun consume(fruit: T) = fruits.add(fruit)
}
    • 使用部位**
fun useProducerConsumer(producerConsumer: ProducerConsumerCrate<*>) {

    // Even though we project * here, T is known to be at least a Fruit
    // because it's the upper bound at the declaration-site.
    val fruit = producerConsumer.produce()   // OK

    // Fruit is guaranteed. Can use functions and properties of Fruit.
    fruit.getColor()                         // OK

    // Consumer not allowed because you don't want to accidentally add
    // oranges, if this crate is a Crate<Apple>.
    producerConsumer.consume(Fruit())        // Error
}

结论

输入不变式Crate<T>的投影:
| 预测|生产|消耗|行为|
| - ------| - ------| - ------| - ------|
| Crate<Fruit>| Fruit| Fruit|生产者和消费者|
| Crate<out Fruit>| Fruit| Nothing|仅限生产商|
| Crate<in Fruit>| Any?| Fruit|仅限消费者|
| Crate<*>| Any?| Nothing|没有生产者也没有消费者|

cnjp1d6j

cnjp1d6j3#

在上下文中,SomeGeneric<*>等价于SomeGeneric<out Any?>,Java等价物是SomeGeneric<? extends Object>
语法称为“星投影”。以下是官方文档:https://kotlinlang.org/docs/reference/generics.html#star-projections

qlzsbp2j

qlzsbp2j4#

为了给其它答案增加一些简单性,首先你需要知道的是Any更大的而且它是**Kotlin类层次结构的根,每个Kotlin类都有Any作为超类,所以他们创建了star projection,这样你就可以把任何类型放到列表中,或者你还不知道的类型。你可以 checkout package kotlin.reflect in以便更仔细地查看。如果Kotlin没有想到这个,我们可以使用<out Any?>这样的外投影或<in Nothing>这样的内投影来进行更深入的研究,你应该看看这个:
star-Projections and How They Work

相关问题