CodeGym /课程 /JAVA 25 SELF /新的集合:SequencedCollection、SequencedSet、SequencedMap

新的集合:SequencedCollection、SequencedSet、SequencedMap

JAVA 25 SELF
第 34 级 , 课程 4
可用

1. 经典集合中的顺序问题

在 Java 中一直存在保证元素顺序的集合(例如 ArrayListLinkedListLinkedHashSetLinkedHashMap),以及不保证顺序的集合(例如 HashSetHashMap)。但这些集合有一个共同缺点:即使其中一些以特定顺序存储元素,标准接口(ListSetMap)也没有提供通用的方法来获取第一个/最后一个元素,或将顺序反转。

例如,对于 List<String>,你可以通过 list.get(0) 拿到第一个元素,但对 SetMap 就不行了——不得不使用迭代器或写额外样板代码。这既不方便,也不利于可读性。

这就好比你有一个带抽屉的柜子,但每次要拿第一个或最后一个抽屉时,都得把所有抽屉重新数一遍!要是有“第一个”和“最后一个”的专用把手就好了,对吧?

SequencedCollection 及相关接口的出现

Java 21 中新增了如下集合接口:

  • SequencedCollection<E>
  • SequencedSet<E>
  • SequencedMap<K, V>

这些接口在标准集合之上扩展,提供了统一的有序操作方式。现在可以针对任何重视顺序的集合编写通用代码,而无需关心具体实现。

这是什么?

  • SequencedCollection:元素具有明确顺序的集合,你可以轻松获取第一个、最后一个元素,并可反转顺序。
  • SequencedSet:同上,但作用于集合(元素唯一)。
  • SequencedMap:同上,但作用于映射(键-值)。

哪些集合现在实现了它们?

Java 21 中,以下标准集合实现了这些新接口:

  • ArrayListLinkedListSequencedCollection
  • LinkedHashSetTreeSetSequencedSet
  • LinkedHashMapTreeMapSequencedMap

这意味着如果你已经在使用这些集合,就会自动获得这些新能力!

2. SequencedCollection、SequencedSet、SequencedMap 的核心方法

方法 SequencedCollection

E getFirst();      // 获取第一个元素
E getLast();       // 获取最后一个元素
SequencedCollection<E> reversed(); // 获取逆序的集合视图

方法 SequencedSet

SequencedCollection 相同的方法,外加 Set 的全部能力。

方法 SequencedMap

Map.Entry<K, V> firstEntry();    // 第一个条目(键-值)
Map.Entry<K, V> lastEntry();     // 最后一个条目(键-值)
SequencedMap<K, V> reversed();   // 逆序的 Map 视图

3. 新接口的使用示例

示例 1:获取第一个和最后一个元素

import java.util.*;

public class SequencedDemo {
    public static void main(String[] args) {
        SequencedCollection<String> sc = new ArrayList<>();
        sc.add("Java");
        sc.add("Python");
        sc.add("Kotlin");

        // 获取第一个和最后一个元素
        String first = sc.getFirst(); // "Java"
        String last = sc.getLast();   // "Kotlin"

        System.out.println("第一个:" + first);
        System.out.println("最后一个:" + last);
    }
}

输出:

第一个:Java
最后一个:Kotlin

示例 2:反转集合

SequencedCollection<String> sc = new LinkedList<>();
sc.add("A");
sc.add("B");
sc.add("C");

SequencedCollection<String> reversed = sc.reversed();
System.out.println(reversed); // [C, B, A]

请注意:reversed() 返回的是集合的逆序视图。如果修改原集合,reversed 视图也会随之改变!

示例 3:使用 SequencedSet

SequencedSet<Integer> set = new LinkedHashSet<>();
set.add(100);
set.add(200);
set.add(300);

System.out.println("第一个元素:" + set.getFirst()); // 100
System.out.println("最后一个元素:" + set.getLast()); // 300

SequencedSet<Integer> reversedSet = set.reversed();
System.out.println(reversedSet); // [300, 200, 100]

示例 4:使用 SequencedMap

SequencedMap<String, Integer> map = new LinkedHashMap<>();
map.put("apple", 5);
map.put("banana", 3);
map.put("cherry", 7);

Map.Entry<String, Integer> first = map.firstEntry();
Map.Entry<String, Integer> last = map.lastEntry();

System.out.println("第一个:" + first.getKey() + " = " + first.getValue()); // apple = 5
System.out.println("最后一个:" + last.getKey() + " = " + last.getValue()); // cherry = 7

SequencedMap<String, Integer> reversedMap = map.reversed();
System.out.println(reversedMap); // {cherry=7, banana=3, apple=5}

4. 这与您的应用有什么关系?

假设在你的教学应用中,你维护了一个已登录用户列表,希望能快速拿到第一个和最后一个用户(例如用于展示“谁最先登录”和“谁最后登录”)。以前你可能会写成:

List<String> users = new ArrayList<>();
// ... 添加用户
String first = users.get(0);
String last = users.get(users.size() - 1);

但如果集合不是列表,而是 LinkedHashSet(元素唯一且保持插入顺序),上述技巧就不适用了:

Set<String> users = new LinkedHashSet<>();
// ... 添加用户
// 如何获取第一个?只能通过迭代器:
String first = users.iterator().next();
// 那最后一个呢?只能遍历所有元素!

现在更简单且更通用:

SequencedSet<String> users = new LinkedHashSet<>();
// ... 添加用户
String first = users.getFirst();
String last = users.getLast();

这不仅减少了代码量,还让代码更易读、更安全。

5. 新接口的关系图

classDiagram
    Collection <|-- SequencedCollection
    List <|-- SequencedCollection
    Set <|-- SequencedSet
    Map <|-- SequencedMap
    SequencedCollection <|-- SequencedSet
    SequencedSet <|-- LinkedHashSet
    SequencedSet <|-- TreeSet
    SequencedCollection <|-- ArrayList
    SequencedCollection <|-- LinkedList
    SequencedMap <|-- LinkedHashMap
    SequencedMap <|-- TreeMap

6. 实用细节

SequencedCollection 的实际优势

  • 统一的顺序操作接口:不必再记住哪里能用 get(0)、哪里要迭代器、哪里根本拿不到第一个元素。
  • 处理队列和栈更方便:可以轻松获取第一个(head)和最后一个(tail)元素。
  • 安全性与可读性:减少因误用集合导致的错误;代码更具自文档性。
  • 快速反转集合:reversed() 让你无需手工操作即可得到逆序视图。
  • 易于维护与扩展代码:未来若想把 ArrayList 替换为 LinkedHashSet,使用 SequencedCollection 的代码无需重写。

实现要点

  • 并非所有集合都实现 SequencedCollection:例如 HashSetHashMap 不保证顺序,因此不会实现这些新接口。
  • 方法可能抛出异常:当集合为空时调用 getFirst()getLast() 会抛出 NoSuchElementException。别忘了先检查集合是否为空!
  • reversed() 是视图而不是副本:对原集合的修改会反映到逆序视图中,反之亦然。
  • 兼容性:这些新接口仅从 Java 21 开始可用。如果你使用更低版本的 Java,暂时无法使用(但这正是升级的好理由!)。
  • 泛型:所有新接口都完全支持泛型,因此可处理任意数据类型。

7. 使用 SequencedCollection 的常见错误

错误 1:对不保证顺序的集合期待有序行为。 如果你尝试将 HashSet 强制转换为 SequencedSet,会得到编译错误——HashSet 没有顺序,也没有实现该接口。

错误 2:忽略空集合。 在空集合上调用 getFirst()getLast() 会抛出异常。在调用这些方法之前,请先检查集合是否为空:

if (!sc.isEmpty()) {
    String first = sc.getFirst();
}

错误 3:对 reversed() 的误解。 reversed() 返回的是视图,而不是副本。如果你修改了 reversed 视图,原集合也会随之改变(反之亦然)。如果没有预期这种行为,可能会得到出乎意料的结果。

错误 4:在旧版 Java 上使用新接口。 如果你的项目使用的 Java 版本低于 Java 21,编译器将找不到这些接口。请在项目设置中检查 JDK 版本!

1
任务
JAVA 25 SELF, 第 34 级, 课程 4
已锁定
智能任务调度器:在操作前检查是否为空 🤖
智能任务调度器:在操作前检查是否为空 🤖
1
任务
JAVA 25 SELF, 第 34 级, 课程 4
已锁定
步骤算法:正向与反向控制 🔄
步骤算法:正向与反向控制 🔄
1
调查/小测验
现代集合第 34 级,课程 4
不可用
现代集合
现代集合与不可变性
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION