我正在研究几种编程语言的集合库中的协变和逆变,并偶然发现了 Kotlin 的Set https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-set/index.html界面。
它被记录为
interface Set<out E> : Collection<E>
这意味着它是协变的——仅“生成”E 对象,遵循 Kotlin 文档 https://kotlinlang.org/docs/reference/generics.html#declaration-site-variance,不消耗它们。
And Set<String>
成为的子类型Set<Any>
.
然而,它有这两种方法:
abstract fun contains(element: E): Boolean
abstract fun containsAll(elements: Collection<E>): Boolean
所以当我创建一个类实现Set<String>
,我必须实施(除了其他)contains(String)
。但后来有人可以用我的课程作为Set<Any>
并打电话set.contains(5)
.
我实际上尝试过这个:
class StringSet : Set<String> {
override val size = 2
override fun contains(element: String): Boolean {
println("--- StringSet.contains($element)")
return element == "Hallo" || element == "World"
}
override fun containsAll(elements: Collection<String>) : Boolean =
elements.all({it -> contains(it)})
override fun isEmpty() = false
override fun iterator() = listOf("Hallo", "World").iterator()
}
fun main() {
val sset : Set<String> = StringSet()
println(sset.contains("Hallo"))
println(sset.contains("xxx"))
//// compiler error:
// println(set.contains(5))
val aset : Set<Any> = sset
println(aset.contains("Hallo"))
println(aset.contains("xxx"))
// this compiles (and returns false), but the method is not actually called
println(aset.contains(5))
}
(在线运行 https://pl.kotl.in/uAKB2qUHH)
事实证明Set<String>
不是“真正”的子类型Set<Any>
,作为set.contains(5)
适用于第二个,但不适用于第一个。
实际上调用 contains 方法甚至可以在运行时工作 - 只是我的实现永远不会被调用,它只是打印false
.
查看接口的源代码,发现这两个方法实际上声明为
abstract fun contains(element: @UnsafeVariance E): Boolean
abstract fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean
这里发生了什么?
Set 是否有一些特殊的编译器魔法?
为什么这没有任何地方记录?