Java的集合框架一直有一个说大不大、说小不小的痛点:明明很多集合是有顺序的,但接口层面却不提供统一的方式来获取头尾元素、移除头尾元素、或者反向遍历。比如List有get(0)但没有直接取最后一个的方法,LinkedHashSet有顺序却没法快捷地拿到最后添加的元素,SortedSet/NavigableSet虽然提供了first()和last(),但与列表完全不兼容。调用方为了获得末尾元素,往往需要根据不同的类型写出不同的代码,可读性大打折扣。
Java 21正式引入的Sequenced Collections彻底解决了这个问题。它为目标集合框架补充了一组关于“有序元素序列”的接口,让所有具有确定遍历顺序的集合都能以统一的方式操作序列的两端。
新接口总览
Sequenced Collections 主要在java.util包中增加了三个核心接口:
SequencedCollection<E>:扩展自Collection,提供了addFirst、addLast、getFirst、getLast、removeFirst、removeLast以及reversed()方法。SequencedSet<E>:扩展自Set和SequencedCollection,保持元素不重复,同时支持有序操作。SequencedMap<K, V>:扩展自Map,提供putFirst、putLast、firstEntry、lastEntry、pollFirstEntry、pollLastEntry以及reversed()等方法。
现在,List、Deque、LinkedHashSet、SortedSet、LinkedHashMap、SortedMap等类都通过新接口获得了统一的两端访问能力。你不再需要记住哪个集合用哪个方法取头尾,直接选择最符合自然语言的getFirst()和getLast()即可。
最简单的转变:统一获取首尾元素
假设你的程序里有一个Collection引用,它可能是一个ArrayList,也可能是LinkedHashSet。以前为了安全地取末尾元素,需要各种条件判断:
// 旧方式
if (collection instanceof List list) {
return list.get(list.size() - 1);
} else if (collection instanceof SortedSet sortedSet) {
return sortedSet.last();
} else {
// 没有统一方式,只能遍历
}
现在,只要该集合实现了SequencedCollection,一行就够:
if (collection instanceof SequencedCollection seq) {
return seq.getLast();
}
来看个实际例子,将多个不同的有序集合统一处理:
SequencedCollection list = new ArrayList(List.of("a", "b", "c"));
SequencedCollection linkedSet = new LinkedHashSet(List.of("x", "y", "z"));
System.out.println(list.getFirst()); // a
System.out.println(list.getLast()); // c
System.out.println(linkedSet.getFirst()); // x
System.out.println(linkedSet.getLast()); // z
反向视图:一行代码反转遍历
新接口提供的reversed()方法返回一个反向的视图,它不是复制整个集合,而是对原集合的一个倒序映射。任何在原集合上的修改都会立刻反映在反向视图里,反之亦然。
var original = new ArrayList(List.of(1, 2, 3));
var reversed = original.reversed();
System.out.println(original); // [1, 2, 3]
System.out.println(reversed); // [3, 2, 1]
original.add(4);
System.out.println(reversed); // [4, 3, 2, 1]
reversed.addFirst(0);
System.out.println(original); // [1, 2, 3, 4, 0] 注意:addFirst在反向视图上是添加在末尾
对于需要反向迭代的场景,使用增强for循环即可:
for (int num : original.reversed()) {
System.out.println(num);
}
这比专门调用Collections.reverse()或者手动从listIterator(size)写起要简洁得多。
SequencedMap:让有序Map操作不再别扭
LinkedHashMap本身保留了插入顺序,但它的API一直缺少直接获取首尾条目的方法。通过SequencedMap,这些操作变得同样简单:
SequencedMap scores = new LinkedHashMap();
scores.put("Alice", 92);
scores.put("Bob", 85);
scores.put("Charlie", 78);
System.out.println(scores.firstEntry()); // Alice=92
System.out.println(scores.lastEntry()); // Charlie=78
// 移除并返回第一个条目
var first = scores.pollFirstEntry();
System.out.println(first); // Alice=92
System.out.println(scores); // {Bob=85, Charlie=78}
对于SortedMap(如TreeMap),SequencedMap同样适用,而且firstEntry、lastEntry的逻辑和已有方法一致,但通过统一接口提高了可替换性。
实战案例:用SequencedMap实现一个简单的LRU缓存
我们借助LinkedHashMap的有序性和SequencedMap的新方法,快速实现一个线程安全的LRU缓存,当容量满时自动淘汰最近最少使用的条目。
import java.util.LinkedHashMap;
import java.util.SequencedMap;
public class LRUCache extends LinkedHashMap {
private final int capacity;
public LRUCache(int capacity) {
super(capacity + 1, 0.75f, true); // access order
this.capacity = capacity;
}
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > capacity;
}
// 通过SequencedMap获取最近最少使用的条目(即头元素)
public Map.Entry getLRU() {
return ((SequencedMap) this).firstEntry();
}
// 获取最近使用的条目(尾元素)
public Map.Entry getMostRecent() {
return ((SequencedMap) this).lastEntry();
}
public static void main(String[] args) {
LRUCache cache = new LRUCache(3);
cache.put("a", "apple");
cache.put("b", "banana");
cache.put("c", "cherry");
System.out.println(cache);
cache.get("a"); // 触发访问,a变为最近使用
cache.put("d", "date"); // 容量满,淘汰最老的b
System.out.println("LRU: " + cache.getLRU().getKey()); // c
System.out.println("Most recent: " + cache.getMostRecent().getKey()); // d
}
}
上面的代码利用LinkedHashMap的访问顺序模式,并直接调用SequencedMap的firstEntry()和lastEntry()来观察缓存两端的元素。这种实现方式比单纯依赖keySet().iterator().next()更加语义化。
案例二:优先级任务队列中的头尾管理
再来看一个场景:你需要维护一个任务队列,支持正常按优先级出队,但也提供快速插入紧急任务到队首的能力。可以用LinkedHashSet保持插入顺序并配合SequencedSet:
SequencedSet taskQueue = new LinkedHashSet();
taskQueue.add("发送日报");
taskQueue.add("备份数据库");
taskQueue.add("清理日志");
// 紧急任务插到最前
taskQueue.addFirst("修复线上故障");
System.out.println(taskQueue); // [修复线上故障, 发送日报, 备份数据库, 清理日志]
// 取出并移除最后一个(最不紧急)
String lastTask = taskQueue.removeLast();
System.out.println("移除最后一个: " + lastTask); // 清理日志
如果使用PriorityQueue,它本身不保证顺序,但SortedSet(例如TreeSet)可以实现SequencedSet,从而在保持排序的同时也能快捷访问首尾元素。
兼容性与使用建议
Sequenced Collections 是 Java 21 中的最终特性,没有预览阶段,可以直接用于生产。所有主要的集合类(包含在java.base模块中)都已经实现了新接口。不过,如果你需要向下兼容一些第三方库提供的自实现集合,可以通过instanceof检查接口是否存在:
if (collection instanceof SequencedCollection seq) {
// 使用新方法
} else {
// 降级处理
}
由于新接口默认提供了方法实现(尤其是addLast、getFirst等),对于自定义集合来说,实现这些接口也只需要很少的工作量。
总结
Sequenced Collections 虽然不像虚拟线程、模式匹配那样引人瞩目,但它实实在在填补了Java集合框架中“有序操作”这一长期缺失的抽象层。它没有引入新概念,只是把散落在不同实现类中的偶发方法收拢成了统一契约,让代码更符合直觉、更少出错。
如果你的项目已经升级到JDK 21,强烈建议你在日常编码中优先使用这些新方法——它们不仅减少样板代码,还能让很多算法(比如队列、栈、缓存)的意图更加明朗。对于正在学习Java的新人来说,了解这套接口也意味着能更快地上手标准库,而不必在不同集合类型的方法表中反复对照。

