JDK 21 新变化之 Sequenced Collections
背景
JEP 431: Sequenced Collections 中提到我们("我们"应该是指 JDK 的一些开发者)为 sequenced collections/sequenced sets/sequenced maps 添加了新的接口,并将这些新接口融合进了现有的集合类型体系中 ⬇️
We define new interfaces for sequenced collections, sequenced sets, and sequenced maps, and then retrofit them into the existing collections type hierarchy.

本文会对这个变化进行探讨。
要点
以下 3 个接口都是 JDK 21 中新增的,这些接口提供了对"头""尾"操作以及对逆序遍历的支持 ⬇️
java.util.SequencedCollectionjava.util.SequencedSetjava.util.SequencedMap
正文
类图对比
通过对比以下类/接口的类图,我们看出 JDK 21 (和 JDK 20 相比) 新增了哪些接口。
java.util.TreeSetjava.util.LinkedHashSetjava.util.Listjava.util.Dequejava.util.TreeMapjava.util.LinkedHashMap
在 JDK 20 中
在 JDK 20 中,画出的类图如下 ⬇️
请注意:以下的类/接口在类图中被忽略了
java.lang.Cloneablejava.io.Serializable
在 JDK 21 中
在 JDK 21 中,画出的类图如下 ⬇️
请注意:以下的类/接口在类图中被忽略了
java.lang.Cloneablejava.io.Serializable
在上方的类图中,我把 JDK 21 中新增的接口用橙色标了出来。
由此可见,JDK 21 中新增了以下几个接口
java.util.SequencedCollectionjava.util.SequencedSetjava.util.SequencedMap
为何会有这样的变化
JEP 431: Sequenced Collections 里对新增这些接口的原因做了详细的描述,读者朋友如果有兴趣的话,可以看一看。下面我结合 JEP 431: Sequenced Collections 一文说说自己的理解。
理由一: 处理首尾元素的方法不统一
在 JDK 21 之前,当我们遇到 List/Deque/SortedSet/LinkedHashSet 时,应该如何获取它们的 第一个 /最后一个 元素呢?下表(表格的原始内容来自 JEP 431: Sequenced Collections)进行了总结 ⬇️
| 接口/类 | 如何获取第一个元素 | 如何获取最后一个元素 |
|---|---|---|
List |
list.get(0) |
list.get(list.size() - 1) |
Deque |
deque.getFirst() |
deque.getLast() |
SortedSet |
sortedSet.first() |
sortedSet.last() |
LinkedHashSet |
linkedHashSet.iterator().next() |
缺少直接的支持 |
可以看出,在使用这些接口/类时,获取第一个元素的方式不统一,获取最后一个元素的方法也不统一(LinkedHashSet 里甚至无法直接获取最后一个元素)。如果我们要开发一个基于 Collection 接口的工具类,这个工具类支持获取 List/Deque/SortedSet 的最后一个元素,那么它的代码也许会是这样 ⬇️
java
import java.util.Deque;
import java.util.List;
import java.util.SortedSet;
public class CollectionUtils {
static <E> E getLast(List<E> list) {
return list.get(list.size() - 1);
}
static <E> E getLast(Deque<E> deque) {
return deque.getLast();
}
static <E> E getLast(SortedSet<E> sortedSet) {
return sortedSet.last();
}
}
这样的代码不优雅,以 List/Deque 为例,它们都是 Collection,但是 Collection 过于通用,其中没有定义获取最后一个元素的方法(Collection 里也不应该定义获取最后一个元素的方法)。所以如果能在 Collection 之下增加一层抽象,在新增的这一层支持获取最后一个元素的操作,那么 CollectionUtils 里对 List/Deque 的处理就可以统一起来 ⬇️ (下图中用 ANewLayer 来表示新增的这一层抽象)

理由二: 倒序遍历的方式不统一
有的时候,我们需要用倒序遍历的方式来处理 Collection 的实例。
NavigableSet 提供了 descendingSet() 方法以支持倒序遍历 ⬇️
java
for (var e : navSet.descendingSet()) {
process(e);
}
对 Deque 而言,可以用如下的方式来倒序遍历 ⬇️
java
for (var it = deque.descendingIterator(); it.hasNext();) {
var e = it.next();
process(e);
}
对 List 而言,我们可以用 ListIterator 来倒序遍历 ⬇️
java
for (var it = list.listIterator(list.size()); it.hasPrevious();) {
var e = it.previous();
process(e);
}
而 LinkedHashSet 没有提供对倒序遍历的支持。
类似地,为 Collection 中的元素生成倒序的 stream,也常常是不容易的。如果在 Java Collections Framework 的体系中增加一些抽象,可以使倒序遍历变得方便和统一(例如新增某个接口,在这个接口中定义 reversed() 方法)。

请注意:上图只是示意图,实际上要新增多个新接口
新增的接口
1. SequencedCollection
请注意:这一小节是我自己翻译过来的,不一定准确,且我删减了不少内容,建议读者朋友还是读一读 JEP 431: Sequenced Collections 中的原始描述
SequencedCollection 是 Collection 的子接口,SequencedCollection 中有"第一个元素","最后一个元素"这样的概念,而在"第一个元素"和"最后一个元素"之间的所有元素都有"前驱"和"后继",SequencedCollection 支持对"头"和"尾"的 添加/读取/删除 操作,也支持"从头到尾"以及"从尾到头"的方式来遍历它的所有元素。
java
public interface SequencedCollection<E> extends Collection<E> {
// new method
SequencedCollection<E> reversed();
// methods promoted from Deque
void addFirst(E);
void addLast(E);
E getFirst();
E getLast();
E removeFirst();
E removeLast();
}
新添加的 reversed() 方法会返回原 Collection 的一个倒序的视图。
SequencedCollection 中的以下方法原本定义在 Deque 里。这些方法支持对"头"和"尾"的 添加/读取/删除 操作。
void addFirst(E)void addLast(E)E getFirst()E getLast()E removeFirst()E removeLast()
2. SequencedSet
请注意:这一小节是我自己翻译过来的,不一定准确,且我删减了不少内容,建议读者朋友还是读一读 JEP 431: Sequenced Collections 中的原始描述
SequencedSet 是 SequencedCollection 和 Set 的子接口,它的实例中没有重复的元素。
java
public interface SequencedSet<E> extends Set<E>, SequencedCollection<E> {
SequencedSet<E> reversed(); // covariant override
}
以 SortedSet 为例,它的元素没有显式的位置,所以它无法支持 addFirst(E) 和 addLast(E) 这样的方法(这两个方法定义在 SequencedCollection 中)。所以 addFirst(E)/addLast(E) 可以抛出 UnsupportedOperationException。
3. SequencedMap
请注意:这一小节是我自己翻译过来的,不一定准确,且我删减了不少内容,建议读者朋友还是读一读 JEP 431: Sequenced Collections 中的原始描述
SequencedMap 是 Map 的子接口 ⬇️
csharp
public interface SequencedMap<K,V> extends Map<K,V> {
// new methods
SequencedMap<K,V> reversed();
SequencedSet<K> sequencedKeySet();
SequencedCollection<V> sequencedValues();
SequencedSet<Entry<K,V>> sequencedEntrySet();
V putFirst(K, V);
V putLast(K, V);
// methods promoted from NavigableMap
Entry<K, V> firstEntry();
Entry<K, V> lastEntry();
Entry<K, V> pollFirstEntry();
Entry<K, V> pollLastEntry();
}
SequencedMap 中的以下方法原本是定义在 NavigableMap 接口中的。这些方法支持对"头"和"尾"进行 读取/删除 操作。
Entry<K, V> firstEntry()Entry<K, V> lastEntry()Entry<K, V> pollFirstEntry()Entry<K, V> pollLastEntry()
其他
如何生成本文中的 Mermaid 类图
我在 [Java] 如何自动生成简单的 Mermaid 类图 一文中描述了如何用代码来生成 Mermaid 类图,在 [Java] 如何自动生成简单的 Mermaid 类图 中提供的 java 代码的基础上,用以下命令可以生成本文所使用的前两张类图。本文中所使用的其他类图,其内容比较简单,不必借助代码来生成。
bash
java ClassDiagramGenerator -i 'java.lang.Cloneable' -i 'java.io.Serializable' 'java.util.TreeSet' 'java.util.LinkedHashSet' 'java.util.List' 'java.util.Deque' 'java.util.TreeMap' 'java.util.LinkedHashMap'
请注意:文中第二张类图中有特殊的 style,而掘金的文档似乎不支持对类图中 style 进行调整,我是参考了 如何调整 Mermaid 类图中指定节点的 style,在 mermaid.live/ 里画出对应的类图。一个调整 style 的简单例子如下

text
classDiagram
Iterable <|-- Collection
Collection <|-- List
Collection <|-- ANewLayer
ANewLayer <|-- List
ANewLayer <|-- Deque
Collection <|-- Queue
Queue <|-- Deque
<<interface>> Iterable
<<interface>> Collection
<<interface>> List
<<interface>> Queue
<<interface>> Deque
<<interface>> ANewLayer
class ANewLayer:::someclass
classDef someclass fill:#f96