前端视角 Java Web 入门手册 2.4:集合框架 Collections Framework

220 阅读5分钟

在编程语言中集合通常是一种数据结构,用于存储多个元素。集合可以用于各种问题的解决,例如搜索、排序、过滤等,在数据结构与算法的学习中经常需要开发者手动实现各种适合的集合,比如链表、队列等

Java 通过数组来满足基本的存储和操作需求,但数组有许多限制性的问题

  1. 固定大小:一旦创建数组的大小就不能改变,这对于在开发过程中不能预估数据数量的情况是不便的
  2. 操作有限:数组原生操作往往只有基本的 CRUD,但更高层次的数据处理功能(例如排序、搜索、分割等)需要手动实现
  3. 类型限制:虽然数组支持存储对象,但在基本使用中,每个数组只能存储相同类型的数据,缺乏对多类型数据的直接支持

为了能够更灵活、更高效地管理复杂的数据集合,Java 提供了丰富的集合框架

  1. 动态大小:集合可以自动进行动态大小调整,灵活性更高。比如 ArrayList 可以在添加元素时自动扩容。
  2. 丰富的数据结构与接口:集合框架中提供多种接口和类,包括 ListSetMap 等,可以根据具体需求选择合适的数据结构
  3. 强大的实用方法:集合类附带丰富的 API,可以实现诸如排序、搜索、过滤、转换等非常复杂的操作,代码简洁且高效

集合框架中的主要接口

  • Map:存储键值对映射关系。常见实现类有 HashMapLinkedHashMapTreeMap
  • List:代表有序的元素集合,允许重复元素。常见实现类有 ArrayListLinkedList
  • Set:代表不允许重复的无序集合。常见实现类有 HashSetLinkedHashSetTreeSet
  • Queue:代表一个先进先出的集合,常见实现类有 PriorityQueueLinkedList

JavaScript 没有集合的明确概念,有几个相关的对象

  • Object:无序的键值对集合,字符串或者数字作为键
  • Map:有序键值对集合,可以用任意类型做键
  • Array:顺序存储、随机访问的元素集合,使用数字做索引
  • Set:无序、不重复元素集合

Collection 接口

Collection 是集合框架的根接口,定义了最基本的集合操作,如添加、删除、判断是否包含元素等。所有单个元素集合(不包含键值对的集合)的接口,如 ListSetQueue 等,都继承自 Collection 接口。Collection 接口包含几个常用方法

  • boolean add(E e)
  • boolean remove(Object o)
  • boolean contains(Object o)
  • int size()
  • void clear()
  • Iterator<E> iterator()
  • boolean isEmpty()
Collection<String> collection = new ArrayList<>();
collection.add("Apple");
collection.add("Banana");
collection.add("Cherry");

System.out.println("集合大小: " + collection.size());
System.out.println("是否包含 Banana: " + collection.contains("Banana"));

collection.remove("Banana");
System.out.println("移除后是否包含 Banana: " + collection.contains("Banana"));

迭代器

Iterable 接口 是 Java 集合框架中最基本的接口之一,它定义了一个返回 Iterator 的方法,使得实现类能够被迭代器遍历。所有实现了 Collection 接口的类都可以使用 for-each 循环,因为 Collection 接口继承自 Iterable

IterableIterator 是集合框架中紧密相关的两个接口,它们协同工作,实现对集合元素的遍历

  • Iterable 提供访问路径:通过 iterator() 方法,Iterable 提供了获取 Iterator 的途径
  • Iterator 执行遍历操作:Iterator 使用 hasNext() 和 next() 方法,逐一访问集合中的元素
public interface Iterable<T> {
    // 返回一个用于遍历该 Iterable 元素的 Iterator
    Iterator<T> iterator();

    // 对该 Iterable 的每个元素执行给定的操作
    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

    // 返回一个 Spliterator,该对象用于支持并行遍历
    default Spliterator<T> spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }
}
public interface Iterator<E> {
    // 判断是否还有下一个元素
    boolean hasNext();

    // 返回下一个元素
    E next();

    // 移除当前元素
    default void remove() {
        throw new UnsupportedOperationException("remove");
    }

    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
}

ArrayList 遍历示例

List<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add("orange");

Iterator<String> it = list.iterator();

while (it.hasNext()) {
    String s = it.next();
    System.out.println(s);
}

Map 接口

Map 接口用于存储键值对(Key-Value)的映射关系,每个键只能出现一次,不同键可以对应相同的值,通过键可以快速访问对应的值。包含几个常用方法

  • V put(K key, V value)
  • V get(Object key)
  • V remove(Object key)
  • boolean containsKey(Object key)
  • boolean containsValue(Object value)
  • Set<K> keySet()
  • Collection<V> values()
  • Set<Map.Entry<K, V>> entrySet()
Map<String, Integer> ageMap = new HashMap<>();
ageMap.put("Alice", 30);
ageMap.put("Bob", 25);
ageMap.put("Charlie", 35);
ageMap.put(null, 40); // 允许一个 null 键
ageMap.put("Diana", null); // 允许多个 null 值

System.out.println("年龄表: " + ageMap);
// 输出: 年龄表: {null=40, Alice=30, Bob=25, Charlie=35, Diana=null}

// 获取值
Integer age = ageMap.get("Alice");
System.out.println("Alice 的年龄: " + age); // 输出: Alice 的年龄: 30

遍历 Map

Map 不能直接实现 Iterable 接口,因为 Iterable 接口要求实现 iterator() 方法,返回一个元素序列,而 Map 本身存储键和值的两个独立集合,但不以单个序列的形式将它们提供出来。

虽然 Map 不直接实现 Iterable 接口,但可以通过以下几种方式访问 Map 的内容:

keySet()

可以通过 Map 的 keySet() 方法获取一个包含所有键的 Set 视图,然后可以对这个键集进行遍历

Map<String, Integer> map = new HashMap<>();
map.put("A", 1);
map.put("B", 2);
map.put("C", 3);

for (String key : map.keySet()) {
    System.out.println("Key: " + key + ", Value: " + map.get(key));
}

values()

可以通过 Map 的 values() 方法获取一个包含所有值的 Collection 视图,遍历时直接操作值

for (Integer value : map.values()) {
    System.out.println("Value: " + value);
}

entrySet()

通过 Map 的 entrySet() 方法获取一个包含所有键值对的 Set<Map.Entry<K, V>> 视图,这也是最直接的方式来同时访问键和值

for (Map.Entry<String, Integer> entry : map.entrySet()) {
    System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}

map.forEach()

Java 8 为 Map 接口添加了forEach方法,它接受一个BiConsumer函数式接口作为参数,这使得可以更方便地遍历 Map 的键值对

map.forEach((key, value) -> {
    System.out.println("Key: " + key + ", Value: " + value);
});