Java 集合框架详解:从基础概念到实战应用 Collection部分

Java 集合框架(Collection Framework)是 Java 语言中用于存储、管理和操作一组对象的核心API,它提供了统一的接口规范和丰富的实现类,极大地简化了数据处理流程。集合框架主要分为两大体系:单列集合(Collection)双列集合(Map),二者在数据存储结构和使用场景上存在本质区别。

一、集合框架整体架构

Java 集合框架的顶层设计遵循“接口定义规范、类实现功能”的原则,确保不同实现类之间具有良好的可替换性。其核心结构如下:

集合体系 顶层接口 主要子接口 常见实现类 核心特征
单列集合(Collection) Collection<E> List<E>Set<E> ArrayList<E>LinkedList<E>HashSet<E>LinkedHashSet<E>TreeSet<E> 存储单个元素,元素间是“个体”关系
双列集合(Map) Map<K,V> HashMap<K,V>LinkedHashMap<K,V>TreeMap<K,V>Hashtable<K,V> HashMap<K,V>LinkedHashMap<K,V>TreeMap<K,V>Hashtable<K,V> 存储键值对(Key-Value),元素间是“映射”关系

二、单列集合:Collection 体系深度解析

Collection<E> 是所有单列集合的顶层接口,它定义了单列集合的通用操作方法(所有子接口和实现类都会继承这些方法),同时衍生出 ListSet 两大子接口,二者在元素存储规则上差异显著。

2.1 Collection 接口核心特性与通用方法

Collection<E> 接口本身不能直接实例化(接口无法创建对象),但它提供的方法是所有单列集合的“通用工具”,包括元素的添加、删除、判断、遍历等操作。

2.1.1 常用方法清单
方法签名 功能描述 返回值/说明
boolean add(E e) 向集合中添加一个元素 添加成功返回 true,失败返回 false(如 Set 集合添加重复元素)
void clear() 清空集合中所有元素 无返回值,集合变为空
boolean remove(Object o) 删除集合中指定的元素 删除成功返回 true,不存在该元素返回 false
boolean contains(Object o) 判断集合中是否包含指定元素 包含返回 true,否则返回 false
boolean isEmpty() 判断集合是否为空(元素数量为 0) 空返回 true,否则返回 false
int size() 获取集合中元素的数量 返回元素个数(int 类型)
Object[] toArray() 将集合转换为 Object 类型的数组 返回 Object 数组,需手动强转至目标类型
<T> T[] toArray(T[] a) 将集合转换为指定类型的数组 返回目标类型数组,避免强转风险
void forEach(Consumer<? super E> action) 基于 Lambda 表达式遍历集合 无返回值,需传入 Lambda 消费型接口
2.1.2 方法使用示例代码
import java.util.ArrayList;
import java.util.Collection;

public class CollectionDemo {
    public static void main(String[] args) {
        // 1. 创建 Collection 实现类对象(ArrayList)
        Collection<String> coll = new ArrayList<>();

        // 2. 添加元素
        coll.add("Java");
        coll.add("Python");
        coll.add("C++");
        System.out.println("添加元素后:" + coll); // 输出:[Java, Python, C++]

        // 3. 判断元素是否存在
        boolean hasJava = coll.contains("Java");
        System.out.println("是否包含 Java:" + hasJava); // 输出:true

        // 4. 获取元素数量
        int size = coll.size();
        System.out.println("集合元素个数:" + size); // 输出:3

        // 5. 转换为指定类型数组(避免强转)
        String[] arr = coll.toArray(new String[0]);
        System.out.println("转换后的数组第 1 个元素:" + arr[0]); // 输出:Java

        // 6. 删除元素
        boolean removed = coll.remove("Python");
        System.out.println("删除 Python 是否成功:" + removed); // 输出:true
        System.out.println("删除元素后:" + coll); // 输出:[Java, C++]

        // 7. 清空集合
        coll.clear();
        System.out.println("清空后是否为空:" + coll.isEmpty()); // 输出:true
    }
}

2.2 Collection 集合的三种遍历方式

遍历是集合操作的核心场景,Collection 提供了三种常用遍历方式,适用于不同需求。

2.2.1 迭代器(Iterator)遍历

迭代器是 Collection 接口的专属遍历工具,通过 iterator() 方法获取,支持“判断是否有下一个元素”和“获取下一个元素”的操作,且是唯一支持在遍历中安全删除元素的方式(避免并发修改异常)。

核心方法

  • boolean hasNext():判断集合中是否还有未遍历的元素,有则返回 true
  • E next():获取集合中的下一个元素,若已无元素则抛出 NoSuchElementException
  • void remove():删除 next() 方法刚获取的元素(必须在 next() 之后调用,否则报错)。

代码示例

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class IteratorDemo {
    public static void main(String[] args) {
        Collection<String> coll = new ArrayList<>();
        coll.add("苹果");
        coll.add("香蕉");
        coll.add("橙子");

        // 1. 获取迭代器对象
        Iterator<String> it = coll.iterator();

        // 2. 遍历集合
        while (it.hasNext()) {
            // 3. 获取当前元素
            String fruit = it.next();
            System.out.println(fruit);

            // 4. 安全删除元素(若用 coll.remove(fruit) 会抛出 ConcurrentModificationException)
            if ("香蕉".equals(fruit)) {
                it.remove();
            }
        }

        System.out.println("删除后的集合:" + coll); // 输出:[苹果, 橙子]
    }
}
2.2.2 增强 for 循环(for-each)遍历

增强 for 循环是 JDK 5 引入的语法糖,底层仍依赖迭代器,语法简洁,适用于“仅遍历、不删除”的场景(若在遍历中调用集合的 remove() 方法,会抛出并发修改异常)。

语法格式

for (元素数据类型 变量名 : 集合/数组) {
    // 对变量的操作
}

代码示例

import java.util.ArrayList;
import java.util.Collection;

public class ForEachDemo {
    public static void main(String[] args) {
        Collection<Integer> coll = new ArrayList<>();
        coll.add(10);
        coll.add(20);
        coll.add(30);

        // 增强 for 循环遍历
        for (int num : coll) {
            System.out.println(num); // 依次输出:10、20、30
        }
    }
}
2.2.3 Lambda 表达式遍历

JDK 8 引入的 forEach() 方法,结合 Lambda 表达式,可实现“一行代码遍历”,语法极简,是现代 Java 开发的首选方式(同样不支持遍历中直接删除元素,需借助迭代器)。

代码示例

import java.util.ArrayList;
import java.util.Collection;

public class LambdaForEachDemo {
    public static void main(String[] args) {
        Collection<String> coll = new ArrayList<>();
        coll.add("张三");
        coll.add("李四");
        coll.add("王五");

        // Lambda 表达式遍历(Consumer 消费型接口,接收元素并处理)
        coll.forEach(name -> System.out.println("姓名:" + name));

        // 简化写法(方法引用)
        coll.forEach(System.out::println);
    }
}

2.3 并发修改异常(ConcurrentModificationException)及解决方案

在遍历集合时,若同时通过集合对象(如 coll.remove())修改集合结构(添加/删除元素),会触发迭代器的“快速失败”机制,抛出 ConcurrentModificationException。其本质是迭代器遍历过程中,集合的修改次数(modCount)与迭代器的预期修改次数(expectedModCount)不一致。

2.3.1 问题复现
import java.util.ArrayList;
import java.util.Collection;

public class ConcurrentModificationDemo {
    public static void main(String[] args) {
        Collection<String> coll = new ArrayList<>();
        coll.add("A");
        coll.add("B");
        coll.add("C");

        // 增强 for 循环中调用 coll.remove(),触发异常
        for (String s : coll) {
            if ("B".equals(s)) {
                coll.remove(s); // 抛出 ConcurrentModificationException
            }
        }
    }
}
2.3.2 三种解决方案
  1. 迭代器 remove() 方法(推荐):通过迭代器删除刚获取的元素,会同步更新 expectedModCount,避免异常。

    Iterator<String> it = coll.iterator();
    while (it.hasNext()) {
        String s = it.next();
        if ("B".equals(s)) {
            it.remove(); // 安全删除
            //注意:这里使用的是迭代器的remove方法 it.remove(); 
            //上面错误案例中使用的是集合的remove方法 coll.remove(s); 。
        }
    }
    
  2. 倒序遍历删除(仅适用于 List,因需索引):倒序遍历不会因删除元素导致后续元素索引偏移,避免漏删或越界。

    List<String> list = new ArrayList<>(coll);
    for (int i = list.size() - 1; i >= 0; i--) {
        if ("B".equals(list.get(i))) {
            list.remove(i); // 安全删除
        }
    }
    
  3. 删除后 i-- 调整索引(仅适用于 List 普通 for 循环):删除元素后,将索引 i 减 1,抵消元素前移的影响。

    List<String> list = new ArrayList<>(coll);
    for (int i = 0; i < list.size(); i++) {
        if ("B".equals(list.get(i))) {
            list.remove(i);
            i--; // 关键:避免跳过下一个元素
        }
    }
    

三、List 集合:有序、可重复、有索引的单列集合

List<E>Collection 的子接口,核心特征是有序(元素存储顺序与添加顺序一致)、可重复(允许元素值相同)、有索引(支持通过索引操作元素)。它在 Collection 方法基础上,新增了大量基于索引的操作方法,常见实现类为 ArrayListLinkedList

3.1 List 接口特有方法(基于索引)

方法签名 功能描述 返回值/说明
void add(int index, E element) 在指定索引位置插入元素 无返回值,原索引及后续元素后移
E remove(int index) 删除指定索引位置的元素 返回被删除的元素,后续元素前移
E set(int index, E element) 替换指定索引位置的元素 返回被替换的旧元素
E get(int index) 获取指定索引位置的元素 返回该索引对应的元素,索引越界抛出 IndexOutOfBoundsException
int indexOf(Object o) 查找元素首次出现的索引 找到返回索引,未找到返回 -1
int lastIndexOf(Object o) 查找元素最后出现的索引 找到返回索引,未找到返回 -1

代码示例

import java.util.ArrayList;
import java.util.List;

public class ListMethodDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("北京");
        list.add("上海");
        list.add("广州");

        // 1. 插入元素(索引 1 位置插入“深圳”)
        list.add(1, "深圳");
        System.out.println("插入后:" + list); // 输出:[北京, 深圳, 上海, 广州]

        // 2. 替换元素(索引 2 位置替换为“杭州”)
        String oldCity = list.set(2, "杭州");
        System.out.println("被替换的旧元素:" + oldCity); // 输出:上海
        System.out.println("替换后:" + list); // 输出:[北京, 深圳, 杭州, 广州]

        // 3. 获取元素(索引 3)
        String city = list.get(3);
        System.out.println("索引 3 的元素:" + city); // 输出:广州

        // 4. 查找元素首次出现的索引
        int index = list.indexOf("深圳");
        System.out.println("深圳的索引:" + index); // 输出:1

        // 5. 删除指定索引元素
        String removedCity = list.remove(3);
        System.out.println("删除的元素:" + removedCity); // 输出:广州
        System.out.println("删除后:" + list); // 输出:[北京, 深圳, 杭州]
    }
}

3.2 List 两大实现类:ArrayList 与 LinkedList

ArrayListLinkedListList 接口最常用的实现类,但二者底层数据结构完全不同,导致性能特点和应用场景差异极大。

3.2.1 底层结构与性能对比
对比维度 ArrayList LinkedList
底层结构 动态数组(基于数组实现,容量不足时自动扩容) 双向链表(每个节点包含前驱、后继指针和元素值)
查询速度 快(通过索引直接访问,时间复杂度 O(1)) 慢(需从表头/表尾遍历到目标节点,时间复杂度 O(n))
增删速度 慢(尾部增删快,中间增删需移动大量元素,O(n)) 快(仅需修改节点指针,中间增删无需移动元素,O(1))
内存占用 低(数组连续存储,无额外指针开销) 高(每个节点需存储前驱、后继指针,额外开销大)
扩容机制 初始容量 10,扩容时默认扩大至原容量的 1.5 倍 无扩容机制(链表节点动态创建,按需分配)
3.2.2 LinkedList 特有方法(首尾操作)

由于 LinkedList 基于双向链表实现,它新增了大量针对“表头”和“表尾”的高效操作方法,非常适合实现队列(FIFO)和栈(LIFO)。

方法签名 功能描述
void addFirst(E e) 在链表头部添加元素(栈的 push 操作)
void addLast(E e) 在链表尾部添加元素(队列的 offer 操作)
E getFirst() 获取链表头部元素(不删除)
E getLast() 获取链表尾部元素(不删除)
E removeFirst() 删除并返回链表头部元素(队列的 poll 操作、栈的 pop 操作)
E removeLast() 删除并返回链表尾部元素

代码示例:用 LinkedList 实现队列和栈

import java.util.LinkedList;

public class LinkedListDemo {
    public static void main(String[] args) {
        // 1. 用 LinkedList 实现队列(先进先出 FIFO)
        LinkedList<String> queue = new LinkedList<>();
        queue.addLast("任务1"); // 入队
        queue.addLast("任务2");
        queue.addLast("任务3");
        System.out.println("队列元素:" + queue); // 输出:[任务1, 任务2, 任务3]
        String task1 = queue.removeFirst(); // 出队
        System.out.println("出队任务:" + task1); // 输出:任务1
        System.out.println("出队后队列:" + queue); // 输出:[任务2, 任务3]

        // 2. 用 LinkedList 实现栈(先进后出 LIFO)
        LinkedList<Integer> stack = new LinkedList<>();
        stack.addFirst(1); // 入栈(push)
        stack.addFirst(2);
        stack.addFirst(3);
        System.out.println("栈元素:" + stack); // 输出:[3, 2, 1]
        int num = stack.removeFirst(); // 出栈(pop)
        System.out.println("出栈元素:" +
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐