四、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 < 0
或index > 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")
注意:
- 如果集合中存在多個相同元素,
remove(Object o)
只刪除第一個; - 如果存儲的是自定義對象,必須重寫
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]
注意:
iterator.next()
必須在iterator.hasNext()
之后調用,否則會拋出NoSuchElementException
;- 遍歷過程中,只能用
iterator.remove()
刪除元素,不能用list.remove()
,否則會破壞迭代器的modCount
和expectedModCount
一致性,導致異常。
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 + 后的推薦方式之一。