我不确定我完全理解SomeGeneric<*>
和SomeGeneric<Any>
之间的区别。我认为“*”代表任何东西(外卡),“Any”代表 ALL 对象继承的对象。所以它们似乎应该是一样的,但它们不是?
答案 0 :(得分:78)
将NAT instance视为一种不仅代表任何类型的方式,而是一些您不知道究竟是什么的固定类型,可能会有所帮助。
例如,类型MutableList<*>
代表某事的列表(您不知道究竟是什么)。因此,如果您尝试在此列表中添加内容,则无法成功。它可以是String
列表,也可以是Int
列表,也可以是其他列表。编译器根本不允许在此列表中放置任何对象,因为它无法验证列表是否接受此类型的对象。但是,如果你试图从这样的列表中获取一个元素,你肯定会得到一个Any?
类型的对象,因为Kotlin中的所有对象都继承自Any
。
答案 1 :(得分:18)
在我认为你暗示的上下文中,SomeGeneric<*>
相当于SomeGeneric<out Any?>
。 Java等价物是SomeGeneric<? extends Object>
。
语法称为“星形投影”。以下是官方文档:https://kotlinlang.org/docs/reference/generics.html#star-projections
答案 2 :(得分:18)
理解星形投影(*
)的关键是首先正确理解另外两种类型的投影in
和out
。之后,星星投影就变得不言自明了。
假设您有一个打算用于存储水果的通用类 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()
?不用担心意外使用不需要的功能?
解决方案是,我们通过在使用站点使用方差修饰符 out
、in
和 *
来投影类型。 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
类用作 {{1} 的生产者或消费者时,给我一个错误因为我不想使用消耗和产生 T
的函数或属性。我只想安全地使用 T-independent 函数和属性,如 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>
、in
或 out
的类似类型也是如此,它将消耗和产生该类型 ({ {1}} 在这种情况下)。这不是投影。这解释了 *
和 Apple
之间的区别,您可以并排比较上面的两个代码片段。
到目前为止,我们看到了 SomeGeneric<*>
类的类型投影 SomeGeneric<Any>
、out
和 in
,它们在声明站点:*
处是不变的。从这里开始,让我们看看星形投影如何处理声明站点上已经是 Crate
和 Crate<T>
且类型参数有边界的类:
声明站点
in
使用站点
out
声明站点
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
}
请注意,Kotlin 不支持下限。所以,在上面的 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
}
(upper bound) 那样有 ConsumerCrate
(lower bound) 这样的东西。
声明站点
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<*> |
没有生产者和消费者 |
就是这样!希望有所帮助。