Kotlin 泛型中“*”和“Any”之间的区别

Posted

技术标签:

【中文标题】Kotlin 泛型中“*”和“Any”之间的区别【英文标题】:Difference between "*" and "Any" in Kotlin generics 【发布时间】:2016-10-19 18:24:27 【问题描述】:

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

【问题讨论】:

【参考方案1】:

将star projection 视为一种不仅可以表示任何类型,还可以表示某些您不知道究竟是什么的固定类型的方法可能会有所帮助。

例如,MutableList<*> 类型表示 something 的列表(您不知道具体是什么)。因此,如果您尝试将某些内容添加到此列表中,您将不会成功。它可能是Strings 的列表,或者Ints 的列表,或者其他东西的列表。编译器根本不允许将任何对象放入此列表中,因为它无法验证该列表是否接受此类型的对象。然而,如果你试图从这样的列表中取出一个元素,你肯定会得到一个 Any? 类型的对象,因为 Kotlin 中的所有对象都继承自 Any

来自asco下面的评论:

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

【讨论】:

这里有一篇有趣的文章,有类似的解释:typealias.com/guides/star-projections-and-how-they-work @fweigl 我认为以这种方式进行区分是不正确的。 List<*> 也可以非常多地包含混合类型。在这里:val list: List<*> = listOf(42, "Bob")。你只是不知道* 是什么,* 很可能是你喜欢的任何父类型,包括Any 本身(就像在我的例子中一样)。唯一重要的区别是,对于像MutableList<T> 这样的逆变或不变类型,在使用* 时禁止“in 操作”,因为您不知道类型参数是什么,因此编译器无法生成任何假设。【参考方案2】:

理解星形投影(*)的关键是先正确理解另外两种类型的投影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 无关的函数,如 @ 987654337@?不用担心意外使用不需要的功能?

解决方案是,我们在使用现场使用方差修饰符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的函数或属性。我只想安全地使用像size()这样的与T无关的函数和属性:

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&lt;Any&gt; 时,你不是在投影,你只是在使用原来的不变类Crate&lt;T&gt;,它可以生产以及消费 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&lt;Apple&gt; 或任何其他没有方差修饰符inout* 的类似类型也是如此,它将消耗并产生该类型(在这种情况下为Apple)。这不是投影。这就解释了SomeGeneric&lt;*&gt;SomeGeneric&lt;Any&gt;的区别,你可以并排比较上面的两个代码sn-ps。


申报现场制作人的明星投影

到目前为止,我们看到 Crate 类在声明站点不变:Crate&lt;T&gt; 的类型投影 outin*。从这里开始,让我们来看看星形投影如何处理声明站点上已经存在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&lt;T&gt;的类型投影:

Projections Produces Consumes Behaviour
Crate&lt;Fruit&gt; Fruit Fruit Producer and Consumer
Crate&lt;out Fruit&gt; Fruit Nothing Producer only
Crate&lt;in Fruit&gt; Any? Fruit Consumer only
Crate&lt;*&gt; Any? Nothing No Producer and No Consumer

就是这样!希望对您有所帮助。

【讨论】:

难以置信的答案!感谢您的写作! 难以置信的答案!您以非常详细和有意义的方式回答了这个问题。 kotlin新手理解不变量非常有用。【参考方案3】:

在我认为你暗示的上下文中,SomeGeneric&lt;*&gt; 等同于SomeGeneric&lt;out Any?&gt;。 Java 等价物是SomeGeneric&lt;? extends Object&gt;

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

【讨论】:

以上是关于Kotlin 泛型中“*”和“Any”之间的区别的主要内容,如果未能解决你的问题,请参考以下文章

scala - 泛型中的任何与下划线

Java泛型中T和问号(通配符)的区别

Java泛型中的“超级”和“扩展”有啥区别[重复]

java 泛型中 TE ... 和 问号(通配符)的区别

Java泛型中extends和super的区别

泛型中? super T和? extends T的区别