Kotlin 仿制药中“ *”和“ Any”的区别

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

34811 次浏览

在您所暗示的上下文中,SomeGeneric<*>等价于 SomeGeneric<out Any?>,而 Java 等价于 SomeGeneric<? extends Object>

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

星体投影看作是一种不仅代表任何类型,而且代表某种你不知道确切是什么的固定类型的方法,可能会有所帮助。

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

来自 阿斯科的评论如下:

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

理解星形投影(*)的关键是首先正确理解另外两种类型的投影 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
}

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

解决方案是,我们在使用现场使用方差修饰符 outin*来投影类型。使用地点仅仅意味着我们使用 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
}

对于没有方差修饰符 inout*Crate<Apple>或任何其他类似类型也是如此,它将消耗和生产该类型(在这种情况下是 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类中,我们不能有类似于 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)
}

使用地点

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 没有生产者就没有消费者

就是这样,希望能有帮助。

为了给其他答案增加一些简单性,您需要知道的第一件事是 Any更大,根据 kotlin 自己的定义它是 Kotlin 类层次结构的根。每个 Kotlin 类都有一个作为超类的 Any。 所以他们创建了 star projection,这样你就可以拥有一个列表的任何类型,或者任何你还不知道类型的东西。你可以检查 package kotlin.reflect,以便进一步了解。 如果 Kotlin 没有想出这个方法,我们可以使用像 <out Any?>这样的外投影或者像 <in Nothing>这样的内投影 如果想深入了解,你应该看看以下内容:

星-投影及其工作原理