【发布时间】:2020-03-26 03:10:29
【问题描述】:
Java 数组不是完全类型安全的,因为它们是协变的:ArrayStoreException 可以出现在别名数组上。另一方面,Java 集合的类型参数是不变的:例如,List<Thread> 不是List<Runnable> 的子类型(这可能有点违反直觉)。
动机似乎与 Lists 和其他集合是可变有关,因此为了保持类型系统的健全,它们的类型参数必须是不变的。
如果编程语言只支持不可变类型,那么类型参数是协变或逆变(但绝不是不变)的类型系统可以工作吗?换句话说,要使用 Scala 表达方差的方式,可以使用 List[+E]、Function[-T, +R]、Map[+K, +V] 等。
我知道有一些较旧的语言(例如GNU Sather)似乎只支持协变/逆变参数类型。
我的一般问题是:在一个完全不可变的数据类型的世界中,是否存在一种特别需要 invariant 参数类型(而不是协变或逆变)的情况?是否有一些不可变数据结构的示例,只有使用不变类型参数才能正确?
【问题讨论】:
-
我很惊讶你写了
Map[-K, +V];当然应该是Map[+K, +V]? (不能在不中断迭代的情况下允许-K。但+K会很好,因为缺少键会隐式映射到 null。) -
唯一自以为是的部分是第一句话半。如果您摆脱有关 Java 列表的东西,一般问题会很有趣且没有意见。
-
回复:“[我的假设]之一是没有空指针,所以我想到的这个虚构版本的 Map 将返回 Optional 或 union 或 sum 类型,如 'V|什么都没有'":好的,但这与此目的相同。回复:“映射基本上是一个用于获取键值的函数,这就是为什么我对 Map 使用与函数相同的类型参数差异”:映射有点像 partial 函数,因为它的键集是其键类型实例集的子集。它通过将缺失的键映射到默认值来弥补这一点,[continued]
-
[continued] 这意味着允许检查密钥类型错误的映射是可以的。回复:“根本不会有像 Iterator 这样的东西,因为它依赖于可变的内部状态”:我认为迭代是集合的基本特征;幸运的是,您可以使用不可变的迭代器(例如,使用
head和tail方法,后者返回新的迭代器)。 -
List<Thread>不是List<Runnable>的子类型与可变性没有太大关系,尽管该问题仅通过可变结构表现出来——不可变结构是一种极端情况。乍一看,没有任何想法,它不是子类型似乎违反直觉,但是当您考虑作为子类型的含义意味着您可以将子类型分配给超类型的变量时,应该很明显为什么不是。
标签: java generics types immutability covariance