质量外展预告 - JDK 21:序列集合不兼容性
Stuart Marks 于 2023 年 5 月 12 日发布
OpenJDK 质量小组正在推广使用 OpenJDK 构建测试 FOSS 项目,以此作为提高发布整体质量的一种方式。此预告是发送给相关项目的定期通信的一部分。如需了解有关该计划的更多信息以及如何加入,请在此处查看。
预告 - JDK 21 - 潜在的序列集合不兼容性
序列集合 JEP已集成到 JDK 21 构建 20 中。
此 JEP 在集合框架的接口层次结构中引入了多个新接口,而这些接口又引入了新的默认方法。当进行此类更改时,它们可能会导致冲突,从而造成源或二进制不兼容性。发生的任何冲突都将出现在实现新集合或对现有集合类进行子类化的代码中。仅使用集合实现的代码基本不受影响。
可能会出现多种类型的冲突。
第一种是简单的命名冲突,如果已存在同名但返回类型或访问修饰符不同的方法。另一种是由于协变重写而导致的不同继承默认方法实现之间的冲突。如果一个类实现了集合框架不同部分的多个接口,则它可能会继承多个默认方法。第三个示例发生在类型推断中。使用类型推断(例如,使用 var
),编译器将为该局部变量推断一个类型。其他代码可能使用必须与推断类型匹配的显式声明类型。对接口层次结构的更改可能会导致不同的推断类型,从而导致不兼容性。
以下示例提供了其他详细信息和缓解潜在不兼容性的策略。
方法命名冲突
具有新方法的新接口已改造到现有的集合类型层次结构中。这些新方法可能会与现有类上的方法冲突。例如,假设你有一个这样的类
class MyList<E> implements List<E> {
/**
* Returns the first element of this list, or an empty
* Optional if this list is empty.
*/
public Optional<E> getFirst() { … }
…
}
}
由 List 继承的 SequencedCollection 接口定义了一个新方法: E getFirst();
由于返回类型不同,这将创建一个源不兼容性。(但这不应该是二进制不兼容性,因为现有二进制文件将继续调用旧方法。)访问修饰符可能会发生相关类型的冲突。例如,包访问方法不能重写接口中定义的方法,而接口必须具有公共访问权限。不幸的是,缓解源不兼容性的唯一方法是重命名冲突方法或重新排列类型层次结构,例如,不再让 MyList
实现 List
。
协变重写冲突
List 和 Deque 接口都提供 reversed() 方法的协变覆盖
List – List<E> reversed();
Deque – Deque<E> reversed();
只要集合实现只实现一个“系列”的集合,例如 List 或 Deque,但不是两者兼有,这就可以正常工作。然而,在某些情况下,集合实现可能会决定同时实现 List 和 Deque
class MyDoubleEndedList<E> implements List<E>, Deque<E> { … }
这在 JDK 20 中可以正常工作,但从 JDK 21 build 20 开始将失败。原因是它继承了 reversed() 方法的冲突定义,一个返回 List,另一个返回 Deque。编译器无法选择一个,因此会发出编译时错误。
解决方案是向 MyDoubleEndedList 添加一个 reversed() 方法,该方法返回既是 List 又 Deque 的类型。这可以是 MyDoubleEndedList 本身(或子类),或者可以是为此目的定义的另一个接口。返回的对象应实现原始集合的反向排序视图。
JDK 本身中有一个示例。java.util.LinkedList 类同时实现了 List 和 Deque,并通过添加一个 reversed()
方法(参见 源代码)解决了此问题,该方法返回 LinkedList 的反向排序视图的实例。反向排序视图的实现有点繁琐,但并不困难。没有涉及实际逻辑。从本质上讲,它覆盖并委托大多数方法到反向排序 List 视图或反向排序 Deque 视图,这两个视图都具有通过这些接口上的默认方法提供的实现。
类型推断
类型推断的结果可能不同,与现有源代码的假设产生冲突。例如,考虑以下代码
List<Collection<String>> m() {
var list = List.of(new ArrayDeque<String>(), List.of("foo"));
return list;
}
这在 JDK 20 上编译,但从 build 20 开始在 JDK 21 上会给出一个编译时错误。原因是随着新集合类型的添加,list 的推断类型发生了变化。
List.of(a, b) 的类型是 List<T>,其中 T 是参数 a 和 b 的公共超类型(更正式地说,是“最小上界”)。在 JDK 20 中,T 是 Collection<String>,因此 list 的类型是 List<Collection<String>>。这与方法的返回类型一致,因此没有错误。
在 JDK 21 build 20 中,SequencedCollection
接口在 List
和 Deque
之上进行了改造,因此新的公共超类型 T
变成了 SequencedCollection<String>
。因此,list
的类型是 List<SequencedCollection<String>>
。这与方法的返回类型不一致,导致编译时错误。
有几种方法可以解决此问题,但也许最简单的方法是为 list
使用显式类型声明,而不是使用 var
List<Collection<String>> list = List.of(new ArrayDeque<String>(), List.of("foo"));
这将声明 list
为与方法的返回类型一致的类型,防止推断出与方法的返回类型不一致的不同类型。
有关 Sequenced Collection 的更多信息,请务必查看以下资源。