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
}
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
}
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
}
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
}
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
}
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
}
为了给其它答案增加一些简单性,首先你需要知道的是Any是更大的而且它是**Kotlin类层次结构的根,每个Kotlin类都有Any作为超类,所以他们创建了star projection,这样你就可以把任何类型放到列表中,或者你还不知道的类型。你可以 checkout package kotlin.reflect in以便更仔细地查看。如果Kotlin没有想到这个,我们可以使用<out Any?>这样的外投影或<in Nothing>这样的内投影来进行更深入的研究,你应该看看这个: star-Projections and How They Work
4条答案
按热度按时间jmo0nnb31#
把星星投影看作是一种表示不是任何类型而是某种你不知道确切是什么的固定类型的方法可能是有帮助的。
例如,类型
MutableList<*>
表示 something 的列表(你不知 prop 体是什么)。所以如果你想在这个列表中添加一些东西,你不会成功。它可能是一个String
的列表,或者是一个Int
的列表,或者其他东西的列表编译器不会我不允许在这个列表中放入任何对象,因为它不能验证这个列表是否接受这种类型的对象。但是,如果你试图从这样的列表中取出一个元素,您肯定会得到一个Any?
类型的对象,因为Kotlin中的所有对象都继承自Any
。此外,
List<*>
可以包含任何类型的对象,但只能包含该类型,因此它可以包含字符串(但只能包含字符串),而List<Any>
可以包含字符串和整数等,所有这些都在同一个列表中。lg40wkob2#
理解星投影(
*
)的关键是首先正确理解另外两种类型的投影in
和out
,然后星投影就变得不言自明了。理解问题
假设你有一个泛型类
Crate
,你打算用它来储存水果,这个类在T
中是不变的,这意味着,这个类可以消费和生产T
(水果)。换句话说,此类具有将T
作为参数的函数(consume),并返回T
(produce)。size()
函数是T无关的,它既不接受T
,也不返回T
:但是,如果您只想将这个已经存在的类用作生产者(
out T
)或消费者(in T
),或者不想使用T
,而只想使用它的与T无关的函数(如size()
),而不用担心意外使用不需要的功能,该怎么办?解决方案是,我们通过在use-site使用变量修饰符
out
、in
和*
来投影类型。* use-site * 简单地表示我们在哪里使用Crate
类。out
投影是T
的生成器通过将
Crate
投影为out
,您将告诉编译器:"当我不小心将Crate
类用作T
的使用者时,因为我只想安全地将该类用作生产者T
,所以给我一个错误":in
投影是T
的使用者通过将
Crate
投影为in
,您将告诉编译器:"当我不小心将Crate
类用作T
的生成者时,因为我只想安全地将该类用作T
的使用者,所以向我显示错误":星形投影不是
T
的生产者和消费者通过将
Crate
投影为*
,您将告诉编译器:"当我不小心将Crate
类用作T
的生产者或消费者时,因为我不想使用消费和生产T
的函数或属性,而只想安全地使用与T无关的函数和属性(如size()
),会给我一个错误":Any
不是投影当你说
Crate<Any>
的时候,你并不是在投射,你只是在使用原始不变类Crate<T>
,它可以 * 产生 * 也可以 * 消耗 *Any
:对于
Crate<Apple>
或任何其他类似的没有变化修饰符in
,out
,或*
的类型也是如此,它将消耗和产生该类型(在这种情况下Apple
)。这不是一个投影。这解释了SomeGeneric<*>
和SomeGeneric<Any>
之间的区别,你可以并排比较上面的两个代码片段。申报地生产者的星形投影
到目前为止,我们看到了
Crate
类的类型投影out
、in
和*
,它们在声明站点是不变的:Crate<T>
。从这里开始,让我们看看星形投影如何处理在声明点已经是in
和out
的类,并使用类型参数边界:申报地消费者星投影
注意Kotlin不支持下界,所以在上面的
ConsumerCrate
类中,我们不能像out T : Orange
(上界)那样有in T super Orange
(下界)。声明位置不变式的星形投影
结论
输入不变式
Crate<T>
的投影:| 预测|生产|消耗|行为|
| - ------| - ------| - ------| - ------|
|
Crate<Fruit>
|Fruit
|Fruit
|生产者和消费者||
Crate<out Fruit>
|Fruit
|Nothing
|仅限生产商||
Crate<in Fruit>
|Any?
|Fruit
|仅限消费者||
Crate<*>
|Any?
|Nothing
|没有生产者也没有消费者|cnjp1d6j3#
在上下文中,
SomeGeneric<*>
等价于SomeGeneric<out Any?>
,Java等价物是SomeGeneric<? extends Object>
。语法称为“星投影”。以下是官方文档:https://kotlinlang.org/docs/reference/generics.html#star-projections
qlzsbp2j4#
为了给其它答案增加一些简单性,首先你需要知道的是
Any
是更大的而且它是**Kotlin类层次结构的根,每个Kotlin类都有Any作为超类,所以他们创建了star projection
,这样你就可以把任何类型放到列表中,或者你还不知道的类型。你可以 checkoutpackage kotlin.reflect
in以便更仔细地查看。如果Kotlin没有想到这个,我们可以使用<out Any?>
这样的外投影或<in Nothing>
这样的内投影来进行更深入的研究,你应该看看这个:star-Projections and How They Work