Java 集合框架详解:从基础概念到实战应用 Collection(List)部分
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> 是所有单列集合的顶层接口,它定义了单列集合的通用操作方法(所有子接口和实现类都会继承这些方法),同时衍生出 List 和 Set 两大子接口,二者在元素存储规则上差异显著。
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 三种解决方案
-
迭代器
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); 。 } } -
倒序遍历删除(仅适用于
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); // 安全删除 } } -
删除后
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 方法基础上,新增了大量基于索引的操作方法,常见实现类为 ArrayList 和 LinkedList。
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
ArrayList 和 LinkedList 是 List 接口最常用的实现类,但二者底层数据结构完全不同,导致性能特点和应用场景差异极大。
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("出栈元素:" +
更多推荐

所有评论(0)