登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  java教程

Java 有序集合怎样统一访问首尾元素

来源:17golang原创

时间:2026-10-18 00:50:33 308浏览 收藏

如果业务代码同时接收 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 也在已知实现或继承关系中。

SequencedCollection 与 List、Deque、SortedSet、LinkedHashSet 以及首尾操作和反向视图的静态关系图
图1:Java 有序集合接口与首尾能力的静态关系图;这是说明图,不是运行截图。

接口参数可以这样收窄:

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() 已经完成复制。

把生产环境边界写进检查清单

SequencedCollection 的首尾读取、可选修改、空集合异常、不可修改集合和反向视图边界图
图2:首尾操作与异常边界的静态关系图;这是说明图,不是运行结果。

引入统一接口后,发布前最值得检查的不是方法名是否记住,而是调用对象是否满足边界条件:

检查项需要确认的事实处理建议
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 版本、空集合、可修改性、视图语义和底层复杂度写入发布检查清单,这样接口升级才不会停留在方法名替换。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>