当前位置: 首页 > news >正文

深入浅出 ArrayList:从基础用法到底层原理的全面解析(中)

四、ArrayList 常用方法实战 —— 从添加到遍历的全场景覆盖

ArrayList 提供了数十个方法,但日常开发中常用的只有 10 个左右,我们按 “元素操作”“集合查询”“遍历方式” 三类来梳理,每个方法都附带示例和注意事项。

4.1 元素添加:add () 与 addAll ()

添加元素是 ArrayList 最基础的操作,核心方法有两个:add(E e)(添加单个元素到尾部)和addAll(Collection<? extends E> c)(添加另一个集合的所有元素到尾部)。

4.1.1 add (E e):尾部添加单个元素

源码核心逻辑:先检查是否需要扩容,再将元素添加到elementData[size],最后size++

public boolean add(E e) {// 确保容量足够(size+1:因为要添加1个元素)ensureCapacityInternal(size + 1);// 将元素添加到数组的size位置(此时size是当前元素个数,索引从0开始)elementData[size++] = e;return true;
}

示例:

List<Integer> list = new ArrayList<>();
list.add(10); // 添加成功,返回true
list.add(20);
System.out.println(list); // 输出:[10, 20]
System.out.println(list.size()); // 输出:2(元素个数)

注意:add()方法永远返回true(因为 ArrayList 允许添加任意元素,不会因 “元素已存在” 而返回 false,这与 Set 的add()不同)。

4.1.2 add (int index, E element):指定索引插入元素

除了尾部添加,还可以在指定索引位置插入元素,但需要注意:插入位置会导致后续元素 “向后移动”,时间复杂度为 O (n),频繁使用会影响性能。

示例:

List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
// 在索引1的位置插入"c"(原索引1的"b"向后移动到索引2)
list.add(1, "c");
System.out.println(list); // 输出:[a, c, b]

注意:如果指定的索引index超出[0, size]范围(即index < 0index > size),会抛出IndexOutOfBoundsException(索引越界异常)。

4.1.3 addAll (Collection<? extends E> c):添加另一个集合的所有元素

示例:

List<String> list1 = new ArrayList<>();
list1.add("a");
list1.add("b");List<String> list2 = new ArrayList<>();
list2.add("c");
list2.add("d");// 将list2的所有元素添加到list1尾部
list1.addAll(list2);
System.out.println(list1); // 输出:[a, b, c, d]

addAll()返回boolean:如果集合因添加操作发生了变化(即 c 不为空),返回true;如果 c 为空,返回false

4.2 元素删除:remove () 与 clear ()

删除元素是 ArrayList 的高频操作,但需要注意 “删除指定索引” 和 “删除指定元素” 的区别,以及删除后元素的移动问题。

4.2.1 remove (int index):删除指定索引的元素

源码核心逻辑:先检查索引是否合法,再计算需要移动的元素个数,通过System.arraycopy将后续元素向前移动 1 位,最后将数组末尾的元素置为 null(帮助 GC 回收)。

public E remove(int index) {// 检查索引是否越界(index >= size则抛出异常)rangeCheck(index);modCount++;// 获取要删除的元素(用于返回)E oldValue = elementData(index);// 计算需要移动的元素个数:size - index - 1(比如删除索引2,size=5,需要移动5-2-1=2个元素)int numMoved = size - index - 1;if (numMoved > 0) {// 将elementData中index+1到size-1的元素,复制到index到size-2的位置(向前移动1位)System.arraycopy(elementData, index+1, elementData, index, numMoved);}// 将数组末尾的元素置为null(帮助GC回收,避免内存泄漏)elementData[--size] = null;// 返回被删除的元素return oldValue;
}

示例:

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c", "d"));
// 删除索引2的元素(即"c")
String removed = list.remove(2);
System.out.println("被删除的元素:" + removed); // 输出:被删除的元素:c
System.out.println(list); // 输出:[a, b, d]

注意:remove(int index)返回的是 “被删除的元素”,而不是boolean,这一点容易和remove(Object o)混淆。

4.2.2 remove (Object o):删除指定元素(第一个匹配项)

remove(int index)不同,remove(Object o)是根据 “元素值” 删除,且只删除 “第一个匹配的元素”(如果元素重复)。

源码核心逻辑:先判断 o 是否为 null(null 元素用 == 判断,非 null 元素用 equals () 判断),找到第一个匹配的索引后,调用fastRemove()删除(逻辑与remove(int index)一致)。

public boolean remove(Object o) {if (o == null) {// 遍历数组,找到第一个null元素for (int index = 0; index < size; index++)if (elementData[index] == null) {fastRemove(index);return true;}} else {// 遍历数组,找到第一个equals(o)为true的元素for (int index = 0; index < size; index++)if (o.equals(elementData[index])) {fastRemove(index);return true;}}// 没找到元素,返回falsereturn false;
}// 快速删除:不检查索引,不返回被删除的元素(内部使用)
private void fastRemove(int index) {modCount++;int numMoved = size - index - 1;if (numMoved > 0)System.arraycopy(elementData, index+1, elementData, index, numMoved);elementData[--size] = null;
}

示例:

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c", "b"));
// 删除第一个"b"
boolean isRemoved = list.remove("b");
System.out.println("是否删除成功:" + isRemoved); // 输出:true
System.out.println(list); // 输出:[a, c, b](只删除了第一个"b")

注意:

  1. 如果集合中存在多个相同元素,remove(Object o)只删除第一个;
  2. 如果存储的是自定义对象,必须重写equals()方法,否则无法正确匹配元素(默认用==判断地址,而非内容)。
4.2.3 clear ():清空所有元素

clear()会删除集合中的所有元素,但不会清空数组容量,只是将数组中的元素置为 null,帮助 GC 回收,size置为 0。

源码:

public void clear() {modCount++;// 将所有元素置为null(帮助GC)for (int i = 0; i < size; i++)elementData[i] = null;// 元素个数置为0size = 0;
}

示例:

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
list.clear();
System.out.println(list); // 输出:[](空集合)
System.out.println(list.size()); // 输出:0

如果需要 “清空元素并缩小数组容量”,可以在clear()后调用trimToSize()(后文会讲)。

4.3 元素查询与修改:get ()、set ()、contains ()

4.3.1 get (int index):获取指定索引的元素

get()是 ArrayList 的核心查询方法,基于索引随机访问,时间复杂度 O (1),源码:

public E get(int index) {// 检查索引是否越界rangeCheck(index);// 返回数组中index位置的元素return elementData(index);
}// 索引越界检查
private void rangeCheck(int index) {if (index >= size)throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

示例:

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
String element = list.get(1);
System.out.println(element); // 输出:b

注意:index必须在[0, size-1]范围内,否则抛出IndexOutOfBoundsException(比如list.get(3),size=3,索引最大为 2,会报错)。

4.3.2 set (int index, E element):修改指定索引的元素

set()用于替换指定索引的元素,返回 “被替换的旧元素”,源码:

public E set(int index, E element) {rangeCheck(index); // 索引越界检查E oldValue = elementData(index); // 获取旧元素elementData[index] = element; // 替换为新元素return oldValue; // 返回旧元素
}

示例:

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
// 将索引1的元素改为"d"
String oldElement = list.set(1, "d");
System.out.println("被替换的旧元素:" + oldElement); // 输出:b
System.out.println(list); // 输出:[a, d, c]
4.3.3 contains (Object o):判断元素是否存在

contains()用于判断集合中是否包含指定元素,返回boolean,底层通过indexOf()实现:

public boolean contains(Object o) {return indexOf(o) >= 0;
}// 查找元素的第一个索引,不存在返回-1
public int indexOf(Object o) {if (o == null) {for (int i = 0; i < size; i++)if (elementData[i] == null)return i;} else {for (int i = 0; i < size; i++)if (o.equals(elementData[i]))return i;}return -1;
}

示例:

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
boolean hasB = list.contains("b");
boolean hasD = list.contains("d");
System.out.println("是否包含b:" + hasB); // 输出:true
System.out.println("是否包含d:" + hasD); // 输出:false

注意:contains()的时间复杂度是 O (n)(需要遍历数组),如果集合元素较多,频繁调用会影响性能。

4.4 ArrayList 的 4 种遍历方式

遍历是集合操作的高频场景,ArrayList 支持多种遍历方式,不同方式的效率和适用场景不同,需根据需求选择。

4.4.1 普通 for 循环(基于索引)

适合需要 “获取索引” 的场景,效率较高(直接随机访问):

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
for (int i = 0; i < list.size(); i++) {System.out.println("索引" + i + ":" + list.get(i));
}

输出:

索引0:a
索引1:b
索引2:c
4.4.2 增强 for 循环(foreach)

语法简洁,无需关心索引,适合 “只遍历元素,不关心索引” 的场景:

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
for (String element : list) {System.out.println(element);
}
a
b
c

注意:foreach 底层是通过迭代器(Iterator)实现的,遍历过程中不能修改集合结构(如 add/remove),否则会抛出ConcurrentModificationException

4.4.3 迭代器(Iterator)

适合 “需要在遍历过程中删除元素” 的场景(必须用迭代器的remove()方法,而非集合的remove()):

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {String element = iterator.next();if ("b".equals(element)) {// 用迭代器的remove()删除,不会抛出异常iterator.remove();}System.out.println(element);
}
System.out.println("删除后的集合:" + list); // 输出:[a, c]

注意:

  1. iterator.next()必须在iterator.hasNext()之后调用,否则会抛出NoSuchElementException
  2. 遍历过程中,只能用iterator.remove()删除元素,不能用list.remove(),否则会破坏迭代器的modCountexpectedModCount一致性,导致异常。
4.4.4 Stream 流遍历(JDK 1.8+)

适合 “需要对元素进行过滤、映射等复杂操作” 的场景,语法简洁,支持链式调用:

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c", "d"));
// 过滤出长度为1的元素,转为大写后遍历输出
list.stream().filter(element -> element.length() == 1).map(String::toUpperCase).forEach(System.out::println);

输出:

A
B
C
D

Stream 流遍历的优势在于 “功能性编程”,能快速实现复杂的元素处理逻辑,是 JDK 1.8 + 后的推荐方式之一。

http://www.dtcms.com/a/350035.html

相关文章:

  • 深度剖析 Grok2 开源:技术原理与创新洞察
  • 奶茶品牌ESG实践:从绿色供应链到可持续竞争力
  • 离线可用的网络急救方案
  • [RK3568][Android11] Android->默认显示设置->系统->开发者选项(不用连续点击版本号)
  • FunASR基础语音识别工具包
  • 学习Python第13天
  • .NET表格控件Spread .NET v18.0——支持富文本、增强PDF导出
  • 【Linux基础知识系列】第一百零七篇 - 使用crontab设置定期任务
  • 【文献阅读】SparseGPT: Massive Language Models Can be Accurately Pruned in One-Shot
  • 【保姆级】AutoCAD 2026安装包下载及详细图文安装教程!
  • Mysql杂志(一)
  • 大模型中常用的提示词框架
  • Python爬虫第五课:selenium自动化爬虫实战
  • ICCV 2025|TRACE:无需标注,用3D高斯直接学习物理参数,从视频“预知”未来!
  • 从2D序列帧到3D体积感:我用AE+UE5 Niagara构建次世代风格化VFX工作流
  • AWS ECS 成本优化完整指南:从分析到实施的最佳实践
  • 嵌入式第三十七天(TCP补充,应用层协议(HTTP))
  • 物联网时序数据库IoTDB架构解析
  • 告别“复制粘贴”式换肤:我用Adobe XD组件变体与CC库,构建多品牌设计系统架构
  • Android用Coil 3检查媒体资源是否有效,Kotlin
  • 【Github】Windows原始hosts文件内容及加速访问Github的方法
  • Linux系统操作编程——http
  • Zabbix Vs. Grafana
  • 在压力测试中如何确定合适的并发用户数?
  • ASP.NET Core 中的构建者模式
  • uniapp中加载.urdf后缀的3D模型(three.js+urdf-loader)
  • 灰狼算法+四模型对比!GWO-CNN-BiLSTM-Attention系列四模型多变量时序预测
  • day62 Floyd 算法 A * 算法
  • 【GPT入门】第58课 感性认识Imdeploy介绍与实践
  • GPT-5评测