我正在研究几种编程语言的集合库中的协变和矛盾,偶然发现了Kotlin的Set界面。
记录为
interface Set<out E> : Collection<E>
这意味着它是协变的–仅“产生” E个对象following the Kotlin documentation,而不消耗它们。
然后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))
}
因此,事实证明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有一些特殊的编译器魔术吗? 为什么在任何地方都没有记录?
答案 0 :(得分:5)
out
修饰符形式的声明位置协方差错过了一个有用的用例,即确保通常将作为参数传递的实例合理地传递到此处。 contains
函数就是一个很好的例子。
在Set.contains
的特定情况下,@UnsafeVariance
批注用于确保函数接受E
的实例,并传递不是{将{1}}放入element
毫无意义-E
的所有正确实现都将始终返回contains
。 Set
的实现不应该存储传递给false
的{{1}},因此永远不要从其他任何具有返回类型Set
的函数中返回它。因此,正确实施的element
不会在运行时违反差异限制。
contains
注释实际上可以抑制编译器方差冲突,就像在E
位置使用Set
投影类型参数一样。
最好在this blog post中描述其动机:
@UnsafeVariance
批注有时,我们需要在类中禁止声明站点的方差检查。例如,要使
out
具有类型安全性,同时保持只读集为协变,我们必须这样做:in
这给
@UnsafeVariance
的实现者带来了一些责任,因为在执行此检查的情况下,取消了元素的实际类型可能在运行时完全是任何东西,但有时必须获得方便的签名。请在下面查看有关集合类型安全性的更多信息。因此,我们为此目的在类型上引入了
Set.contains
批注。故意将其设置成较长的长度,并脱颖而出以警告再次滥用它。
博客的其余部分还明确提到使用interface Set<out E> : Collection<E> {
fun contains(element: @UnsafeVariance E): Boolean
}
对contains
进行签名可以提高类型安全性。
引入@UnsafeVariance
的替代方法是使contains
接受@UnsafeVariance
,但是此选项缺少对@UnsafeVariance
呼叫的类型检查,该检查不会检测到{{1 }}由于不是contains
的实例而不能出现在集合中。