kotlin - 当 contains() 取 E 时,Kotlin 的 Set 如何是协变的?
问题描述
我正在研究几种编程语言的集合库中的协变和逆变,偶然发现了 Kotlin 的Set接口。
它被记录为
interface Set<out E> : Collection<E>
这意味着它是协变的——仅按照 Kotlin 文档“生产” E 对象,而不是使用它们。
并Set<String>
成为 的子类型Set<Any>
。
然而,它有这两种方法:
abstract fun contains(element: E): Boolean
abstract fun containsAll(elements: Collection<E>): Boolean
所以当我创建一个实现类时Set<String>
,我必须实现(除了其他人)contains(String)
。但后来有人可以将我的课程用作 aSet<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))
}
(在线运行)
所以事实证明这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 有什么特殊的编译器魔法吗?为什么这在任何地方都没有记录?
解决方案
修饰符形式的声明站点协变out
错过了一个有用的用例,即确保作为参数传递的实例通常可以在此处传递。contains
函数就是一个很好的例子。
在特定情况下Set.contains
,@UnsafeVariance
注释用于确保函数接受 的实例E
,因为传递element
未传入的实例是E
没有contains
意义的——所有正确的实现都Set
将始终返回false
。的实现Set
不应该存储element
传递给的contains
,因此不应该从任何其他具有返回类型的函数返回它E
。因此,正确实施Set
不会违反运行时的方差限制。
@UnsafeVariance
注释实际上抑制了编译器差异冲突,例如在 -position 中使用-projectedout
类型参数in
。
这篇博文最好地描述了它的动机:
@UnsafeVariance
注解有时我们需要在我们的类中抑制声明站点的差异检查。例如,为了
Set.contains
在保持只读集协变的同时确保类型安全,我们必须这样做:interface Set<out E> : Collection<E> { fun contains(element: @UnsafeVariance E): Boolean }
这给 的实现者带来了一些责任
contains
,因为通过抑制这种检查,元素的实际类型在运行时可能是任何东西,但有时需要实现方便的签名。请参阅下面有关集合的类型安全的更多信息。
@UnsafeVariance
因此,我们为此目的引入了类型注解。它被故意延长并突出以警告不要滥用它。
博客文章的其余部分还明确提到contains
using的签名@UnsafeVariance
提高了类型安全性。
引入的替代方法@UnsafeVariance
是继续contains
接受Any
,但是此选项缺少对contains
调用的类型检查,该类型检查将检测element
由于不是E
.
推荐阅读
- android - Recycle View 的当前帖子无法将正确的数据传输到另一个活动
- azure - 将 Spring Boot Tomcat azure k8s 部署转换为独立应用程序
- php - 将 mysql 语句存储到我可以传递给 html 表单的变量中
- java - 如何使用 CURL 将多格式文件和 JSON 发布到 Springboot Restful Webservice
- javascript - 使用节点和动态生成的 HTML 修改 HTML 文件
- database - 如何将 CSV 文件导入 DolphinDB?
- python - 打开和关闭 Firefox 浏览器的两个实例
- javascript - Node.js - 无法使用 $(this) 从 jquery 访问把手元素
- java - 为什么这个while循环与“and”运算符一起使用而不与“or”一起使用?
- keras - DepthwiseConv2D 和 SeparableConv2D 的区别