如果业务代码同时接收 List、Deque 和保持插入顺序的集合,过去常见的写法是分别调用 get(0)、get(size() - 1)、peekFirst() 或迭代器。Java 21 引入的 SequencedCollection 把“有明确遇到顺序、能访问两端、可以反向查看”统一成一个接口。
因此,统一访问首尾元素的做法是:把参数声明为 SequencedCollection,读取时使用 getFirst() 和 getLast(),需要改动时使用对应的 add 或 remove 方法,反向遍历时使用 reversed()。空集合和不可修改集合仍然有明确的异常边界,不能因为接口统一就省略处理。
官方参考地址:https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/SequencedCollection.html
先确认集合是否有明确遇到顺序
SequencedCollection 不是所有 Collection 的父接口。它要求集合拥有稳定的 encounter order,也就是可以解释哪个元素在前、哪个元素在后。官方 API 中,List、Deque、SortedSet 和 SequencedSet 都属于这条接口体系;常见的 ArrayList、LinkedList、LinkedHashSet 和 TreeSet 也在已知实现或继承关系中。
接口参数可以这样收窄:
import java.util.SequencedCollection;
import java.util.List;
public final class BoundaryReader {
public static String describe(SequencedCollection values) {
// 先把空集合当作调用方约束处理,避免 getFirst/getLast 抛出异常。
if (values.isEmpty()) {
return "empty";
}
// 这两个方法表达的是遇到顺序的两端,不依赖具体实现是数组还是链表。
return "first=" + values.getFirst() + ", last=" + values.getLast();
}
public static void main(String[] args) {
// List 在 Java 21+ 中实现了 SequencedCollection 的首尾契约。
List queue = List.of("compile", "package", "publish");
System.out.println(describe(queue));
}
}
这里的重点不是把所有集合都改成同一个实现,而是让调用方依赖“有序集合”这项能力。若参数只需要判断包含关系,就继续使用更宽的 Collection;只有需要首尾或反向顺序时,才使用 SequencedCollection。
用 getFirst 和 getLast 读取两端
getFirst() 与 getLast() 的语义比手写索引更清楚。对于 List,官方实现分别对应第 0 个元素和 size() - 1 个元素;对于其他有序集合,调用方不需要知道其内部存储方式。两个方法在集合为空时都会抛出 NoSuchElementException,所以应该在边界处显式选择“提前返回”还是“让异常交给上层”。
import java.util.NoSuchElementException;
import java.util.SequencedCollection;
public final class EndpointReader {
public static E requireLast(SequencedCollection values) {
// 这个方法的契约是“必须存在最后一个元素”,异常可以交给调用方记录。
return values.getLast();
}
public static E findFirstOrNull(SequencedCollection values) {
// 查询型调用不把空集合当作系统故障,先返回 null 表示没有结果。
if (values.isEmpty()) {
return null;
}
return values.getFirst();
}
public static E readWithBoundary(SequencedCollection values) {
try {
// 需要保留统一异常语义时,直接调用 getFirst。
return values.getFirst();
} catch (NoSuchElementException ex) {
// 这里补充业务上下文,避免上层只看到一个无描述的异常。
throw new IllegalStateException("集合不能为空", ex);
}
}
}
生产代码里不建议为了“保险”同时使用索引和首尾方法。列表已经是 SequencedCollection 时,统一使用首尾方法能让代码在切换到 Deque 或有序集合实现后保持同样的调用面。
用四个两端方法表达修改意图
除了读取,接口还提供 addFirst、addLast、removeFirst 和 removeLast。它们都是可选操作:集合实现可以不支持修改,或者只支持其中一部分。因此声明为 SequencedCollection 后,仍然不能假定任何集合都可写。
import java.util.ArrayList;
import java.util.SequencedCollection;
public final class EndpointMutation {
public static void addAuditMarkers(SequencedCollection values,
E firstMarker, E lastMarker) {
// 只有调用方确认集合可修改时,才使用两端插入操作。
values.addFirst(firstMarker);
values.addLast(lastMarker);
}
public static E takeFirst(SequencedCollection values) {
// removeFirst 同时完成读取和删除,空集合会抛出 NoSuchElementException。
return values.removeFirst();
}
public static void main(String[] args) {
// ArrayList 支持这些操作,但头部插入或删除可能移动后续元素。
ArrayList values = new ArrayList<>();
values.add("middle");
addAuditMarkers(values, "first", "last");
System.out.println(values);
}
}
实现选择也要看访问模式:频繁操作两端时,Deque 通常更贴近意图;需要索引访问时,List 更直观。接口统一解决的是调用语义,不会把不同实现的时间复杂度自动变成相同。
用 reversed 获取反向视图
reversed() 返回按相反遇到顺序观察原集合的视图。它不是要求调用方复制一份新集合,因此适合只需要反向遍历、从末端读取或把现有逻辑转成反向方向的场景。
import java.util.ArrayList;
import java.util.List;
public final class ReverseViewExample {
public static void main(String[] args) {
List stages = new ArrayList<>(List.of("compile", "test", "publish"));
// reversed 返回反向顺序的视图,原列表仍然是同一个数据来源。
List reverse = stages.reversed();
System.out.println(reverse); // [publish, test, compile]
System.out.println(reverse.getFirst()); // publish
System.out.println(reverse.getLast()); // compile
// 视图是否允许修改取决于底层列表;不要把 reversed 当成不可变副本。
reverse.set(0, "rollback");
System.out.println(stages); // [compile, test, rollback]
}
}
反向视图的首尾也会反转:原集合的最后一个元素,在视图里就是第一个元素。官方文档还说明,若实现允许视图修改,修改会写回底层集合;底层集合的变更是否立即对视图可见,则取决于具体实现。需要快照时,应显式复制,例如 new ArrayList<>(values.reversed()),而不是误以为 reversed() 已经完成复制。
把生产环境边界写进检查清单
引入统一接口后,发布前最值得检查的不是方法名是否记住,而是调用对象是否满足边界条件:
| 检查项 | 需要确认的事实 | 处理建议 |
|---|---|---|
| JDK 版本 | 这些方法自 Java 21 起可用 | 编译工具链和运行时都升级到支持的版本,或保留旧版本适配层 |
| 空集合 | 读取和删除首尾会抛出 NoSuchElementException | 查询场景先判断 isEmpty,必需数据场景保留带上下文的异常 |
| 修改权限 | 两端修改是可选操作 | 对 List.of 等不可修改集合不要调用 add/remove;必要时先复制 |
| 反向视图 | reversed 返回视图,不等于深拷贝 | 需要独立快照时显式创建新集合 |
| 性能预期 | 统一接口不改变底层实现的复杂度 | 根据两端操作、索引访问和迭代模式选择 List 或 Deque |
如果项目仍要兼容 Java 17,不能直接把 getFirst() 或 reversed() 写进目标字节码;可以在边界层继续使用旧接口,待运行时与编译器升级后再切换。这个兼容判断应在构建配置和发布清单里固定下来,避免开发机能编译、生产环境却无法启动。
常见问题
SequencedCollection 和 Collection 有什么区别?
Collection 只表达元素集合;SequencedCollection 还表达明确的遇到顺序、两端操作和反向视图。没有顺序要求的参数不要强行升级为后者。
getFirst 能替代所有的 get(0) 吗?
只有当调用者关心“第一个元素”而不是固定索引时才适合替代。若业务确实需要第 3 个或第 N 个元素,仍然使用列表的索引 API。
reversed 会创建新集合吗?
不会把它当作新集合。它返回反向顺序的视图,读写行为和底层变更可见性由具体实现决定;需要隔离数据时请显式复制。
为什么 LinkedHashSet 也能统一访问首尾?
它维护明确的遇到顺序,并属于有序集合接口体系。统一接口让调用方不必依赖“链表”或“数组”这样的内部实现名称。
总结一下:需要按顺序访问两端时,把依赖声明为 SequencedCollection;读取用 getFirst/getLast,修改用四个两端方法,反向查看用 reversed。最后把 JDK 版本、空集合、可修改性、视图语义和底层复杂度写入发布检查清单,这样接口升级才不会停留在方法名替换。

PHP PDO 怎样避免 bindValue 类型导致查询异常