[Java] JDK 21 新变化之 Sequenced Collections

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
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 <> AbstractCollection <> AbstractSet <> AbstractMap <> Iterable <> Collection <> Set <> SortedSet <> NavigableSet <> List <> Queue <> Deque <> Map <> SortedMap <> NavigableMap
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 中的原始描述

SequencedCollectionCollection 的子接口,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 中的原始描述

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

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

SequencedMapMap 的子接口 ⬇️

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

参考资料

相关推荐
小胖霞4 小时前
从零开始:在阿里云 Ubuntu 服务器部署 Node+Express 接口(基于公司 GitLab)
前端·后端
特拉熊4 小时前
23种设计模式之原型模式
后端·架构
trow4 小时前
ConcurrentHashMap线程安全实现详解
java·后端
trow4 小时前
HashMap核心原理与源码剖析
java·后端
Java中文社群4 小时前
阿里出手了:全免费!号称国内版ClaudeCode?
后端·ai编程
false4 小时前
.net 验证码 + TP6 通过接口调用实现
后端
kida_yuan5 小时前
【以太来袭】2. 节点设计与部署
后端·区块链·以太坊
渣哥5 小时前
Spring Boot 本质揭秘:约定优于配置 + 自动装配
javascript·后端·面试
9ilk6 小时前
【同步/异步 日志系统】--- 介绍
后端·中间件