Paŭ*_*ann 3 collections covariance kotlin
我在几种编程语言的集合库中研究协变和矛盾时,偶然发现了Kotlin的Set接口。
它记录为
interface Set<out E> : Collection<E>
Run Code Online (Sandbox Code Playgroud)
这意味着它是协变的– 遵循Kotlin文档,仅“产生” E对象,而不消耗它们。
并Set<String>成为的子类型Set<Any>。
但是,它具有以下两种方法:
abstract fun contains(element: E): Boolean
abstract fun containsAll(elements: Collection<E>): Boolean
Run Code Online (Sandbox Code Playgroud)
因此,当我创建一个实现类时Set<String>,我必须(在其他人旁边)实现contains(String)。但是后来有人可以将我的课程用作a Set<Any>和call 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))
}
Run Code Online (Sandbox Code Playgroud)
(在线运行)
因此,事实证明这Set<String>不是的“真实”子类型Set<Any>,因为它set.contains(5)适用于第二个而不是第一个。
实际上,调用contains方法甚至可以在运行时使用-只是永远不会调用我的实现,而只会打印false。
查看接口的源代码,结果发现这两个方法实际上声明为
abstract fun contains(element: @UnsafeVariance E): Boolean
abstract fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean
Run Code Online (Sandbox Code Playgroud)
这里发生了什么?Set有一些特殊的编译器魔术吗?为什么在任何地方都没有记录?
out修饰符形式的声明位置协方差错过了一个有用的用例,即确保将作为参数传递的实例通常明智地传递给此处。这些contains功能就是一个很好的例子。
在特定情况下Set.contains,该@UnsafeVariance注释用于确保该函数接受的一个实例E,作为传递一个element不是E到contains是没有意义的-所有的正确执行Set总是返回false。的实现Set不应存储element传递到的内容contains,因此绝不能从任何具有返回类型的函数中返回它E。因此,正确实施的代码Set在运行时不会违反方差限制。
该@UnsafeVariance注释实际上抑制了编译器方差冲突,如使用out在-projected类型参数in位上。
在此博客文章中最好地描述了它的动机:
@UnsafeVariance注解Sometimes we need to suppress declaration-site variance checks in our classes. For example, to make
Set.containstypesafe while keeping read-only sets co-variant, we had to do it:Run Code Online (Sandbox Code Playgroud)interface Set<out E> : Collection<E> { fun contains(element: @UnsafeVariance E): Boolean }This puts some responsibility on the implementor of
contains, because with this check suppressed the actual type of element may be anything at all at runtime, but it’s sometimes necessary to achieve convenient signatures. See more on the type-safety of collections below.So, we introduced the
@UnsafeVarianceannotation on types for this purpose. It’s been deliberately made long and stands out to warn agains abusing it.
The rest of the blog post also explicitly mentions that the signature of contains using @UnsafeVariance improves type-safety.
The alternative to introducing @UnsafeVariance was to keep contains accepting Any, but this option lacks the type check on contains calls that would detect erroneous calls with elements that can't be present in the set due to not being instances of E.
| 归档时间: |
|
| 查看次数: |
79 次 |
| 最近记录: |