[Java] JDK 21 新变化之 Sequenced Collections

155 阅读8分钟

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.

image.png

本文会对这个变化进行探讨。

要点

以下 3 个接口都是 JDK 21 中新增的,这些接口提供了对“头”“尾”操作以及对逆序遍历的支持 ⬇️

  • java.util.SequencedCollection
  • java.util.SequencedSet
  • java.util.SequencedMap

mermaid-diagram-2025-10-22-201731.png

上图中用橙色标出了新增的 3 个接口在 Java Collections Framework 中的位置。

请注意

  • 有些 Set 不是 java.util.SequencedSet 接口的实现类,例如
    • java.util.HashSet
    • java.util.EnumSet
  • 有些 Map 不是 java.util.SequencedMap 接口的实现类,例如
    • java.util.HashMap
    • java.util.EnumMap

正文

类图对比

通过对比以下类/接口的类图,我们看出 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
classDiagram

Iterable <|-- Collection
Collection <|.. AbstractCollection
Collection <|-- Set
AbstractCollection <|-- AbstractSet
Set <|.. AbstractSet
Set <|-- SortedSet
SortedSet <|-- NavigableSet
AbstractSet <|-- TreeSet
NavigableSet <|.. TreeSet
AbstractSet <|-- HashSet
Set <|.. HashSet
HashSet <|-- LinkedHashSet
Set <|.. LinkedHashSet
Collection <|-- List
Collection <|-- Queue
Queue <|-- Deque
Map <|.. AbstractMap
Map <|-- SortedMap
SortedMap <|-- NavigableMap
AbstractMap <|-- TreeMap
NavigableMap <|.. TreeMap
AbstractMap <|-- HashMap
Map <|.. HashMap
HashMap <|-- LinkedHashMap
Map <|.. LinkedHashMap

<<Abstract>> AbstractCollection
<<Abstract>> AbstractSet
<<Abstract>> AbstractMap
<<interface>> Iterable
<<interface>> Collection
<<interface>> Set
<<interface>> SortedSet
<<interface>> NavigableSet
<<interface>> List
<<interface>> Queue
<<interface>> Deque
<<interface>> Map
<<interface>> SortedMap
<<interface>> NavigableMap
JDK 21

JDK 21 中,画出的类图如下 ⬇️

请注意:以下的类/接口在类图中被忽略了

  • java.lang.Cloneable
  • java.io.Serializable mermaid-diagram-2025-10-22-142508.png

在上方的类图中,我把 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)进行了总结 ⬇️

接口/类如何获取第一个元素如何获取最后一个元素
Listlist.get(0)list.get(list.size() - 1)
Dequedeque.getFirst()deque.getLast()
SortedSetsortedSet.first()sortedSet.last()
LinkedHashSetlinkedHashSet.iterator().next()缺少直接的支持

可以看出,在使用这些接口/类时,获取第一个元素的方式不统一,获取最后一个元素的方法也不统一(LinkedHashSet 里甚至无法直接获取最后一个元素)。如果我们要开发一个基于 Collection 接口的工具类,这个工具类支持获取 List/Deque/SortedSet 的最后一个元素,那么它的代码也许会是这样 ⬇️

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 来表示新增的这一层抽象)

mermaid-diagram-2025-10-22-154758.png

理由二: 倒序遍历的方式不统一

有的时候,我们需要用倒序遍历的方式来处理 Collection 的实例。

NavigableSet 提供了 descendingSet() 方法以支持倒序遍历 ⬇️

for (var e : navSet.descendingSet()) {
    process(e);
}

Deque 而言,可以用如下的方式来倒序遍历 ⬇️

for (var it = deque.descendingIterator(); it.hasNext();) {
    var e = it.next();
    process(e);
}

List 而言,我们可以用 ListIterator 来倒序遍历 ⬇️

for (var it = list.listIterator(list.size()); it.hasPrevious();) {
    var e = it.previous();
    process(e);
}

LinkedHashSet 没有提供对倒序遍历的支持。

类似地,为 Collection 中的元素生成倒序的 stream,也常常是不容易的。如果在 Java Collections Framework 的体系中增加一些抽象,可以使倒序遍历变得方便和统一(例如新增某个接口,在这个接口中定义 reversed() 方法)。

image.png

请注意:上图只是示意图,实际上要新增多个新接口

新增的接口

1. SequencedCollection

请注意:这一小节是我自己翻译过来的,不一定准确,且我删减了不少内容,建议读者朋友还是读一读 JEP 431: Sequenced Collections 中的原始描述

SequencedCollectionCollection 的子接口,SequencedCollection 中有“第一个元素”,“最后一个元素”这样的概念,而在“第一个元素”和“最后一个元素”之间的所有元素都有“前驱”和“后继”,SequencedCollection 支持对“头”和“尾”的 添加/读取/删除 操作,也支持“从头到尾”以及“从尾到头”的方式来遍历它的所有元素。

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 中的原始描述

SequencedSetSequencedCollectionSet 的子接口,它的实例中没有重复的元素。

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 中的原始描述

SequencedMapMap 的子接口 ⬇️

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()

一些常用类是否实现了 SequencedCollection/SequencedSet/SequencedMap 接口

1. ArrayList/LinkedList

ArrayList/LinkedListList 接口的常用实现类,它们在 JDK 21 中的类图如下 ⬇️

mermaid-diagram-2025-10-22-214911.png

上图中用黄色标记的类/接口都可以向上转型为 SequencedCollection。观察上图可知,

  • ArrayListSequencedCollection 接口的实现类
  • LinkedListSequencedCollection 接口的实现类
2. LinkedList/ArrayDeque

LinkedList/ArrayDequeDeque 接口的常用实现类,它们在 JDK 21 中的类图如下 ⬇️

mermaid-diagram-2025-10-22-215347.png

上图中用色标记的类/接口都可以向上转型为 SequencedCollection。观察上图可知,

  • LinkedListSequencedCollection 接口的实现类
  • ArrayDequeSequencedCollection 接口的实现类
3. TreeSet/HashSet/LinkedHashSet

TreeSet/HashSet/LinkedHashSetSet 接口的常用实现类,它们在 JDK 21 中的类图如下 ⬇️

mermaid-diagram-2025-10-22-215857.png

上图中用绿色标记的类/接口都可以向上转型为 SequencedSet。观察上图可知,

  • TreeSetSequencedSet 接口的实现类(TreeSet 也是 SequencedCollection 接口的实现类)
  • LinkedHashSetSequencedSet 接口的实现类(LinkedHashSet 也是 SequencedCollection 接口的实现类)
  • HashSet 不是 SequencedSet 接口的实现类
4. TreeMap/HashMap/LinkedHashMap

TreeSet/HashSet/LinkedHashSetMap 接口的常用实现类,它们在 JDK 21 中的类图如下 ⬇️

mermaid-diagram-2025-10-22-220500.png

上图中用粉色标记的类/接口都可以向上转型为 SequencedMap。观察上图可知,

  • TreeMapSequencedMap 接口的实现类
  • LinkedHashMapSequencedMap 接口的实现类
  • HashMap 不是 SequencedMap 接口的实现类
5. EnumMap/EnumSet

EnumMapMap 接口的常用实现类,EnumSetSet 接口的常用实现类。它们在 JDK 21 中的类图如下 ⬇️

mermaid-diagram-2025-10-23-091517.png

上图中用橙色标记了 SequencedCollection/SequencedSet/SequencedMap 的位置。观察上图可知,

  • EnumMap 不是 SequencedMap 接口的实现类
  • EnumSet 不是 SequencedSet 接口的实现类
小结
全限定类名它是 SequencedCollection 的实现类吗?它是 SequencedSet 的实现类吗?
java.util.ArrayList
java.util.LinkedList
java.util.ArrayDeque
java.util.TreeSet
java.util.HashSet
java.util.LinkedHashSet
java.util.EnumSet
全限定类名它是 SequencedMap 的实现类吗?
java.util.TreeMap
java.util.HashMap
java.util.LinkedHashMap
java.util.EnumMap

其他

如何生成本文中的 Mermaid 类图

我在 [Java] 如何自动生成简单的 Mermaid 类图 一文中描述了如何用代码来生成 Mermaid 类图。用以下命令可以生成本文“要点”这个小节所使用的类图(橙色的 style 是我手动添加的)

java ClassDiagramGenerator 'java.util.List' 'java.util.Deque' 'java.util.NavigableSet' 'java.util.NavigableMap'

请注意:这张类图中需要用到特殊的 style,而掘金的文档似乎不支持对类图中 style 进行调整,我是参考了 如何调整 Mermaid 类图中指定节点的 style,在 mermaid.live/ 里画出了对应的类图。

image.png

所用到的代码如下

classDiagram

Iterable <|-- Collection
Collection <|-- SequencedCollection
SequencedCollection <|-- List
Collection <|-- Queue
Queue <|-- Deque
SequencedCollection <|-- Deque
Collection <|-- Set
SequencedCollection <|-- SequencedSet
Set <|-- SequencedSet
Set <|-- SortedSet
SequencedSet <|-- SortedSet
SortedSet <|-- NavigableSet
Map <|-- SequencedMap
SequencedMap <|-- SortedMap
SortedMap <|-- NavigableMap

<<interface>> Iterable
<<interface>> Collection
<<interface>> SequencedCollection
<<interface>> List
<<interface>> Queue
<<interface>> Deque
<<interface>> Set
<<interface>> SequencedSet
<<interface>> SortedSet
<<interface>> NavigableSet
<<interface>> Map
<<interface>> SequencedMap
<<interface>> SortedMap
<<interface>> NavigableMap

class SequencedCollection:::someclass
class SequencedSet:::someclass
class SequencedMap:::someclass
classDef someclass fill:#f96

用以下命令可以生成本文“类图对比”这个小节所使用的类图(第一次执行时需要将本地的 JDK 调整为 JDK 20,第二次执行时需要将本地的 JDK 调整为 JDK 21

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/ 里画出了对应的类图。

本文其他类图的生成过程也是类似的,就不一一赘述了。

参考资料