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
上图中用橙色标出了新增的 3 个接口在 Java Collections Framework 中的位置。
请注意
- 有些
Set不是java.util.SequencedSet接口的实现类,例如java.util.HashSetjava.util.EnumSet
- 有些
Map不是java.util.SequencedMap接口的实现类,例如java.util.HashMapjava.util.EnumMap
正文
类图对比
通过对比以下类/接口的类图,我们看出 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
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.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 的最后一个元素,那么它的代码也许会是这样 ⬇️
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() 方法以支持倒序遍历 ⬇️
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() 方法)。
请注意:上图只是示意图,实际上要新增多个新接口
新增的接口
1. SequencedCollection
请注意:这一小节是我自己翻译过来的,不一定准确,且我删减了不少内容,建议读者朋友还是读一读 JEP 431: Sequenced Collections 中的原始描述
SequencedCollection 是 Collection 的子接口,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 中的原始描述
SequencedSet 是 SequencedCollection 和 Set 的子接口,它的实例中没有重复的元素。
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 的子接口 ⬇️
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/LinkedList 是 List 接口的常用实现类,它们在 JDK 21 中的类图如下 ⬇️
上图中用黄色标记的类/接口都可以向上转型为 SequencedCollection。观察上图可知,
ArrayList是SequencedCollection接口的实现类LinkedList是SequencedCollection接口的实现类
2. LinkedList/ArrayDeque
LinkedList/ArrayDeque 是 Deque 接口的常用实现类,它们在 JDK 21 中的类图如下 ⬇️
上图中用色标记的类/接口都可以向上转型为 SequencedCollection。观察上图可知,
LinkedList是SequencedCollection接口的实现类ArrayDeque是SequencedCollection接口的实现类
3. TreeSet/HashSet/LinkedHashSet
TreeSet/HashSet/LinkedHashSet 是 Set 接口的常用实现类,它们在 JDK 21 中的类图如下 ⬇️
上图中用绿色标记的类/接口都可以向上转型为 SequencedSet。观察上图可知,
TreeSet是SequencedSet接口的实现类(TreeSet也是SequencedCollection接口的实现类)LinkedHashSet是SequencedSet接口的实现类(LinkedHashSet也是SequencedCollection接口的实现类)HashSet不是SequencedSet接口的实现类
4. TreeMap/HashMap/LinkedHashMap
TreeSet/HashSet/LinkedHashSet 是 Map 接口的常用实现类,它们在 JDK 21 中的类图如下 ⬇️
上图中用粉色标记的类/接口都可以向上转型为 SequencedMap。观察上图可知,
TreeMap是SequencedMap接口的实现类LinkedHashMap是SequencedMap接口的实现类HashMap不是SequencedMap接口的实现类
5. EnumMap/EnumSet
EnumMap 是 Map 接口的常用实现类,EnumSet 是 Set 接口的常用实现类。它们在 JDK 21 中的类图如下 ⬇️
上图中用橙色标记了 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/ 里画出了对应的类图。
所用到的代码如下
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/ 里画出了对应的类图。
本文其他类图的生成过程也是类似的,就不一一赘述了。