上一篇
java list怎么使用
- 后端开发
- 2025-08-11
- 5
Java List常用ArrayList,先创建对象,用add()添加元素,get(index)获取,for循环或迭代器遍历
Java中的List
是java.util
包下的核心接口之一,用于表示有序可重复元素的集合,它继承了Collection
接口,提供了丰富的增删改查操作,并支持通过索引快速访问元素,以下是对其使用方法的全面解析:
核心特性与设计目标
特征 | 描述 |
---|---|
有序性 | 元素按插入顺序存储,可通过索引定位 |
可重复性 | 允许存储相同元素 |
动态扩容 | 多数实现类(如ArrayList )会自动扩展底层数组 |
位置感知 | 支持get(int index) 、set(int index, E element) 等基于索引的操作 |
失败快速原则 | 遍历时若结构被修改(非迭代器自身),会抛出ConcurrentModificationException |
主流实现类对比
实现类 | 底层数据结构 | 适用场景 | 关键优势 | 劣势 |
---|---|---|---|---|
ArrayList |
动态数组 | 频繁随机访问/末尾增删 | O(1)随机访问 内存连续 |
中间插入/删除慢(O(n)) |
LinkedList |
双向链表 | 频繁头尾操作/大量插入/删除 | O(1)头尾插入 灵活扩容 |
随机访问慢(O(n)) |
Vector |
线程安全动态数组 | 多线程环境(已过时) | 同步机制内置 | 性能低于ArrayList |
CopyOnWriteArrayList |
写时复制数组 | 读多写少的并发场景 | 无锁读操作 避免脏读 |
写操作开销大 |
推荐实践:单线程场景优先使用ArrayList
;需频繁插入/删除时选择LinkedList
;多线程场景建议配合Collections.synchronizedList()
包装或使用CopyOnWriteArrayList
。
核心方法详解
基础操作
import java.util.List; import java.util.ArrayList; public class ListDemo { public static void main(String[] args) { // 1. 创建List实例(推荐使用接口编程) List<String> fruits = new ArrayList<>(); // 钻石运算符简化泛型声明 // 2. 添加元素 fruits.add("Apple"); // 尾部追加 fruits.add(0, "Banana"); // 指定位置插入 fruits.addAll(Arrays.asList("Orange", "Grape")); // 批量添加 // 3. 访问元素 System.out.println("第一个元素: " + fruits.get(0)); // 根据索引获取 System.out.println("所有元素: " + fruits); // toString()自动调用 // 4. 修改元素 fruits.set(1, "Mango"); // 替换索引1处的元素 // 5. 删除元素 fruits.remove(2); // 按索引删除 fruits.remove("Grape"); // 按对象删除(需重写equals方法) fruits.clear(); // 清空列表 } }
高级操作
方法签名 | 功能描述 | 时间复杂度 |
---|---|---|
boolean contains(Object o) |
判断是否包含指定元素 | O(n) |
int indexOf(Object o) |
返回首次出现元素的索引(未找到返回-1) | O(n) |
lastIndexOf(Object o) |
返回最后一次出现的索引 | O(n) |
List<E> subList(int from, int to) |
返回指定区间的子视图(原地修改会影响原列表) | O(1) |
void sort(Comparator<? super E> c) |
自然排序或自定义排序 | O(n log n) |
E[] toArray() |
转换为数组 | O(n) |
注意:subList()
返回的是原列表的视图,对子列表的修改会直接影响原列表。
典型使用场景示例
场景1:统计学生成绩排名
List<Integer> scores = new ArrayList<>(Arrays.asList(85, 92, 78, 90, 88)); Collections.sort(scores, Comparator.reverseOrder()); // 降序排序 System.out.println("前三名: " + scores.subList(0, 3)); // [92, 90, 88]
场景2:过滤偶数
List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5)); numbers.removeIf(n -> n % 2 == 0); // 删除所有偶数 → [1, 3, 5]
场景3:批量替换元素
List<String> words = new ArrayList<>(Arrays.asList("cat", "dog", "fish")); for (int i = 0; i < words.size(); i++) { if (words.get(i).length() > 3) { words.set(i, words.get(i).toUpperCase()); // ["CAT", "DOG", "FISH"] } }
性能优化技巧
- 预设初始容量:已知数据量时通过构造函数指定初始容量,避免多次扩容。
// 预估存储1000个元素,减少扩容次数 List<String> largeList = new ArrayList<>(1000);
- 避免频繁插入中间位置:
ArrayList
的add(index, element)
会导致后续元素整体后移,时间复杂度为O(n),若需高频插入,改用LinkedList
。 - 慎用
contains
判断存在性:对于大型列表,改用HashSet
进行存在性校验更高效。 - 遍历方式选择:
- 普通for循环:适合
ArrayList
(内存局部性更好) - 增强for循环:代码简洁,但无法在遍历中删除元素
- Iterator:唯一支持遍历中安全删除的方式
Iterator<String> iter = fruits.iterator(); while (iter.hasNext()) { String fruit = iter.next(); if (fruit.startsWith("A")) { iter.remove(); // 安全删除 } }
- 普通for循环:适合
常见误区与解决方案
误区 | 后果 | 解决方案 |
---|---|---|
直接使用原始类型(未指定泛型) | 编译警告+类型转换异常 | 始终使用泛型参数 |
认为Vector 是线程安全的替代品 |
实际仍需要额外同步措施 | 使用Collections.synchronizedList() 或CopyOnWriteArrayList |
在遍历时调用remove() |
ConcurrentModificationException |
使用Iterator.remove() 或先收集待删除元素再统一处理 |
忽略subList() 的关联性 |
意外修改原列表 | 若需独立副本,调用new ArrayList<>(subList) |
错误比较对象(如自定义对象未重写equals ) |
contains/indexOf 失效 |
确保元素类正确实现hashCode() 和equals() |
相关问答FAQs
Q1: 如何选择ArrayList
还是LinkedList
?
A: 根据操作类型决定:
- 优先选
ArrayList
:如果业务以随机访问为主(如按索引查询),或数据量较小且增长平稳,其内存连续布局带来更好的缓存命中率。 - 优先选
LinkedList
:如果业务以频繁插入/删除操作为主(尤其是中间位置),或数据量极大且稀疏,链表结构在这些场景下效率更高。 - 特殊场景:若既需要快速随机访问又需要频繁插入/删除,可考虑第三方库如Apache Commons Lang的
FastArrayList
。
Q2: 为什么不能直接遍历时调用list.remove(element)
?
A: 因为标准for-each
循环和普通for
循环在遍历时,如果通过list.remove()
直接修改列表结构,会导致预期外的ConcurrentModificationException
,这是因为Java集合框架采用失败快速机制(Fail-Fast):遍历时会记录modCount(修改次数计数器),当检测到结构被非规修改时立即抛出异常,正确的删除方式有两种:
- 使用迭代器的
remove()
方法:这是唯一安全的遍历中删除方式。 - 先标记后删除:先收集所有待删除元素的索引或引用,遍历结束后