Kotlin仿制药中"*"与"Any"的区别

Whe*_*der 82 generics kotlin

我不知道我完全理解之间的差异SomeGeneric<*>SomeGeneric<Any>.我认为"*"代表任何东西(外卡),"Any"代表ALL对象继承的对象.所以它们似乎应该是一样的,但它们不是?

Ale*_*lov 110

星形投影视为一种表示不仅仅是任何类型的方法,而是一些你不知道究竟是什么类型的固定类型可能会有所帮助.

例如,类型MutableList<*>表示某事物的列表(您不知道究竟是什么).因此,如果您尝试在此列表中添加内容,则不会成功.它可以是Strings 的列表,也可以是s的列表Int,或者其他的列表.编译器根本不允许在此列表中放置任何对象,因为它无法验证列表是否接受此类型的对象.但是,如果你试图从这样的列表中获取一个元素,你肯定会得到一个类型的对象Any?,因为Kotlin中的所有对象都继承自Any.

  • 很好的答案,对于一些人来说,添加List <*>可以包含*any*类型的对象,但*只*那个类型,因此它可以包含字符串(但只包含字符串),而List <Any>可以包含字符串和整数等等,都在同一个列表中. (60认同)
  • @fweigl我认为这样区分是不正确的。`List&lt;*&gt;` 也可以包含混合类型。这里:`val list: List&lt;*&gt; = listOf(42, "Bob")`。您只是不知道“*”是什么,而“*”很可能是您喜欢的任何父类型,包括“Any”本身(如我的示例中所示)。唯一重要的区别是,对于像 MutableList&lt;T&gt; 这样的逆变或不变类型,使用 * 时禁止“in 操作”,因为您不静态地知道类型参数是什么,因此编译器可以不要做任何假设。 (2认同)

Yog*_*ity 25

理解星形投影(*)的关键是正确理解其他两种类型的投影inout第一。在那之后,星形投影变得不言自明。


了解问题

假设您有一个Crate打算用于存储水果的通用类。这个类在 中是不变的T。这意味着,这个类可以消费和生产T(水果)。换句话说,这个类具有T作为参数(消费)和返回T(生产)的函数。该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
}
Run Code Online (Sandbox Code Playgroud)

但是,如果您想将这个已经存在的类用作生产者(out T)或消费者(in T),或者不想T仅使用其与 T 无关的函数,该size()怎么办?不用担心意外使用不需要的功能?

解决的办法是,我们采用方差改性剂项目的类型outin*在使用现场。Use-site只是意味着我们在哪里使用这个Crate类。


out 投影是一个生产者 T

通过投影Crateas 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             
}
Run Code Online (Sandbox Code Playgroud)

in 投影是消费者 T

通过投影Crateas 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
}
Run Code Online (Sandbox Code Playgroud)

星投影无生产者,无消费者 T

通过投影Crateas *,您是在告诉编译器:“当我不小心使用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
}
Run Code Online (Sandbox Code Playgroud)

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
}
Run Code Online (Sandbox Code Playgroud)

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


报关现场制作人星投影

到目前为止,我们看到了类型的预测outin并且*Crate这是在声明站点不变类:Crate<T>。从这里开始,让我们找出星形投影如何处理已经存在的类in以及out在声明站点上的具有类型参数边界的类:

申报现场

class ProducerCrate<out T : Fruit> {
    private val fruits = listOf<T>()
    fun produce() : T = fruits.last()
}
Run Code Online (Sandbox Code Playgroud)

使用现场

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
}
Run Code Online (Sandbox Code Playgroud)

申报现场消费者的星形投影

申报现场

class ConsumerCrate<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: 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
}
Run Code Online (Sandbox Code Playgroud)

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


声明站点不变量的星形投影

申报现场

class ProducerConsumerCrate<T : Fruit> {
    private val fruits = mutableListOf<T>()
    fun produce(): T = fruits.last()
    fun consume(fruit: T) = fruits.add(fruit)
}
Run Code Online (Sandbox Code Playgroud)

使用现场

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
}
Run Code Online (Sandbox Code Playgroud)

结论

输入不变量的投影Crate<T>

预测 生产 消耗 行为
Crate<Fruit> Fruit Fruit 生产者和消费者
Crate<out Fruit> Fruit Nothing 仅限生产者
Crate<in Fruit> Any? Fruit 仅限消费者
Crate<*> Any? Nothing 没有生产者和消费者

就是这样!希望有帮助。

  • 令人难以置信的答案!感谢您的来文! (5认同)
  • 令人难以置信的答案!你以非常详细且有意义的方式回答了这个问题。对于 kotlin 新手理解不变量非常有用。 (3认同)
  • 这才是解释应该有的样子!多谢! (3认同)
  • 哇,有人应该要求将其添加到官方 kotlin 文档中......比他们的解释更好 (3认同)

vod*_*dan 22

在我认为你暗示的上下文中,SomeGeneric<*>相当于SomeGeneric<out Any?>.Java等价物是SomeGeneric<? extends Object>.

语法称为"星形投影".以下是官方文档:https://kotlinlang.org/docs/reference/generics.html#star-projections