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.SequencedCollection
java.util.SequencedSet
java.util.SequencedMap
正文
类图对比
通过对比以下类/接口的类图,我们看出 JDK 21
(和 JDK 20
相比) 新增了哪些接口。
java.util.TreeSet
java.util.LinkedHashSet
java.util.List
java.util.Deque
java.util.TreeMap
java.util.LinkedHashMap
在 JDK 20
中
在 JDK 20
中,画出的类图如下 ⬇️
请注意:以下的类/接口在类图中被忽略了
java.lang.Cloneable
java.io.Serializable
在 JDK 21
中
在 JDK 21
中,画出的类图如下 ⬇️
请注意:以下的类/接口在类图中被忽略了
java.lang.Cloneable
java.io.Serializable
在上方的类图中,我把 JDK 21
中新增的接口用橙色标了出来。
由此可见,JDK 21
中新增了以下几个接口
java.util.SequencedCollection
java.util.SequencedSet
java.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