答案 0 :(得分:3)
Kotlin documentation中也解释了这一点:
对于
Foo<in T>
,其中T
是逆变类型参数,Foo<*>
相当于Foo<in Nothing>
。这意味着当Foo<*>
未知时,您无法以安全的方式写T
。
我们有一个班级Foo<T>
,其中包含逆变T
(声明网站),即Foo
仅作为T
的使用者:
class Foo<in T> {
fun accept(t: T) {
println(t)
}
}
我们可以在简单的通用函数中使用这种类型,如下所示:
fun <F> usingFoo(con: Foo<F>, t: F) {
con.accept(t)
}
(使用F
以区分类类型参数T
)
这很好用,因为我们按预期使用Foo
:作为T
的消费者。
现在,您的引言只是说使用con: Foo<*>
类型而不是con: Foo<T>
的参数会产生以下影响:“您无法调用任何具有{{1}的方法在签名“。
因此,以下失败:
T
使用fun <F> usingFoo(con: Foo<*>, t: F) {
con.accept(t) //not allowed!!
}
的实例调用accept
是不安全的,因为我们不知道F
的类型(由明星投影表示)。
Star Projection :有时您想说您对类型参数一无所知,但仍希望以安全的方式使用它。这里安全的方法是定义泛型类型的这种投影,该泛型类型的每个具体实例都是该投影的子类型。
答案 1 :(得分:1)
进一步由用户s1m回答。
class Foo<in T> {
fun accept(t: T) {
println(t)
}
fun doSomething(): String {
return "yay"
}
}
fun <F> usingFoo(con: Foo<*>, t: F) {
// con.accept(t) //not allowed!!
val result = con.doSomething() // OK!
}
fun main() {
val number: Int = 5
usingFoo(Foo<Number>(), number)
}
关于使用
Foo<*>
以一种安全的方式,doSomething()方法显示了一个示例(禁止的方法是使用T类型的方法)。
答案 2 :(得分:1)
让我们了解为什么逆变Consumer<in T>
的星形投影等价于<in Nothing>
的原因。但在此之前,我们需要了解到底什么是明星投影和它是如何为协变Producer<out T>
当你使用一些通用类的星形投影,则没有在使用的功能或性质返回T
,或接受感兴趣T
如从通用类的参数。例如,下面的函数只是返回两个的大List
。我们仅在size
财产兴趣不返回或接受T
,它会返回一个Int
示例
fun getBiggerOfTwo(list1: MutableList<*>, list2: MutableList<*>) : MutableList<*> {
return if (list1.size >= list2.size) list1 else list2
}
<强>为什么使用*
,而不是指定一个类型吗
我们希望保持类型未知,所以我们不会最终使用
T
特定功能意外。例如,在上面的函数,如果我们允许调用list1.add(Something())
,我们最终可能会不小心变异列表,我们只是想比较列表。因此,编译器会帮助我们,当我们调用add()
功能标记错误。除了创建安全性之外,我们的函数也将可重用于各种类型,而不仅仅是某些特定类型。
在以下实施例中,我们将使用Producer
和Consumer
的类生成和使用的各种亚型class Pet(val cutenessIndex: String)
:
声明站点
class Producer<out T> {
private val items = listOf<T>()
fun produce() : T = items.last()
fun size(): Int = items.size
}
使用站点
fun useProducer(star: Producer<*>) {
// Produces Any?, a Pet is not guaranteed because T is unknown
val anyNullable = star.produce() // Not useful
// Can't use functions and properties of Pet.
anyNullable.cutenessIndex // Error
// Can use T-independent functions and properties
star.size() // OK
}
声明站点
class Producer<out T : Pet> {
private val pets = listOf<T>()
fun produce() : T = pets.last()
}
使用站点
fun useProducer(star: Producer<*>) {
// Even though we use * here, T is known to be at least a Pet
// because it's an upper bound at the declaration site.
// So, Pet is guaranteed.
val pet = star.produce() // OK
// Can use properties and functions of Pet.
pet.cutenessIndex // OK
}
声明站点
class Consumer<in T> {
private val items = mutableListOf<T>()
fun consume(item: T) = items.add(item)
fun size(): Int = items.size
}
使用站点
fun useConsumer(consumer: Consumer<*>) {
// Cannot consume anything because
// lower bound is not supported in Kotlin and T is unknown.
consumer.consume(Pet()) // Error
// Can use T-independent functions and properties.
consumer.size() // OK
}
下界不科特林支持。因此,在上面的Consumer
类,我们不能有像in Pet : T
(下限),如我们有out T : Pet
(上限)。我们知道,消费者可以消费T
,它的亚型。 Nothing
是 Kotlin 中所有类型的子类型,就像 Any?
是所有类型的超类型一样。并且因为,在T
是在星形投影未知的,唯一已知的亚型T
是Nothing
。这就是为什么消费者的明星投影只能消耗Nothing
。因此,说Consumer<*>
是一样的东西的话Consumer<in Nothing>
。
就是这样!希望帮助清理的东西了。
答案 3 :(得分:0)
要添加到sm1 ..的绝妙答案,以下是协变类型的示例:
class Foo<out U: Number> {
private var u: U? = null
fun produce(): U? {
return u
}
}
fun usingFoo(foo: Foo<*>) {
foo.produce()
}
fun main() {
usingFoo(Foo<Number>()) //OK
usingFoo(Foo<Any>()) // Not OK
//Direct calls (not using method with *)
Foo<Number>().produce() //OK
Foo<Any>().produce() //Not OK
}
没有星型投影的直接调用中的行为相同,但*基本上表示:我们定义了泛型(Foo),并且在方法使用时(usingFoo),我们仍然不知道具体类型。保留超级类型(在本例中为Number)的原始约束。 *只是说:具体类型在其他地方定义。