一. 介紹 ArrayList是List介面的一個實現類,它是Java程式中最常用的集合之一。在ArrayList內部,它使用一個可變長度的數組來存儲元素。當向ArrayList中添加元素時,如果當前的數組容量不足以容納新增的元素,ArrayList會自動進行擴容操作,創建一個更大的數組,並將原始數 ...
一. 介紹
ArrayList是List介面的一個實現類,它是Java程式中最常用的集合之一。在ArrayList內部,它使用一個可變長度的數組來存儲元素。當向ArrayList中添加元素時,如果當前的數組容量不足以容納新增的元素,ArrayList會自動進行擴容操作,創建一個更大的數組,並將原始數據複製到新數組中。這樣就實現了ArrayList的長度可變性。通過索引可以快速訪問和修改ArrayList中的元素,同時也支持進行插入、刪除等操作。ArrayList允許null值存在。
ArrayList是有序的數組,當你向ArrayList中添加元素時,它們會按照添加的順序進行存儲,並且保持這個順序。因此,你可以通過索引來訪問ArrayList中的元素,並且它們會按照添加的順序進行返回。
ArrayList結構圖:
ArrayList繼承AbstractList<E>,AbstractList是一個抽象類,提供了一些通用的列表操作方法。
ArrayList實現RandomAccess介面,表示允許通過索引直接訪問集合中的元素;
ArrayList實現Cloneable介面,表示可以進行克隆操作;
ArrayList實現Serializable介面,表示可以進行序列化操作。
二. ArrayList的優點
動態增長:ArrayList底層數據結構是Object數組(Object[])。在ArrayList中,每次添加、刪除、查詢等操作都是直接對這個底層的Object數組進行實現。當元素數量超過當前數組長度時,ArrayList會根據需要自動進行擴容操作,通常會創建一個新的更大的數組,並將原有元素複製到新數組中。這樣就能保證ArrayList的高效操作和動態調整大小的特性。
高效的隨機訪問:ArrayList內部以數組形式存儲元素,元素是按照索引順序存儲的,每個元素占據連續的記憶體空間。由於數組具有隨機訪問的特性,通過索引可以直接計算出元素在記憶體中的地址,從而實現快速的隨機訪問操作。因此支持通過索引快速隨機訪問元素。可以使用get()方法通過索引獲取元素,時間複雜度為O(1)。
方便的插入和刪除操作:ArrayList提供了豐富的方法來進行元素的插入和刪除操作。通過add()方法可以在任意位置插入元素,通過remove()方法可以刪除指定位置的元素,或者通過元素值來刪除。
支持多種數據類型:ArrayList可以存儲任何類型的對象,包括基本數據類型的包裝類、自定義對象等。這使得ArrayList成為一個很靈活的集合類,適用於各種場景。
支持迭代操作:ArrayList實現了Iterable介面,因此可以使用增強的for迴圈或者迭代器來遍歷集合中的元素。這使得對ArrayList進行遍歷操作變得非常便捷。
需要註意的是,由於ArrayList的底層實現是數組,所以在頻繁進行插入和刪除操作時,會涉及數組元素的移動,性能可能受到影響。此時可以考慮使用LinkedList等其他集合類來代替ArrayList。
三. 源碼分析
成員方法:
private static final int DEFAULT_CAPACITY = 10;
預設初始化容器 = 10;
privatestaticfinal Object[] EMPTY_ELEMENTDATA = {};
用於空實例的共用空數組 ,一般在構造方法指定空容器,或在elementData、size()為0的時候使用。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}
共用的空數組實例用於預設大小的空實例。我們通過區分它與EMPTY_ELEMENTDATA來知道在添加第一個元素時需要擴容多少。
ransient Object[] elementData;
ArrayList的內部使用數組緩衝區來存儲其元素。
ArrayList的容量是該數組緩衝區的長度。當第一個元素被添加到elementData為DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空ArrayList時,它將會被擴展到DEFAULT_CAPACITY(預設容量)。
private int size;
ArrayList的大小(包含的元素數)
構造方法:
1. 構造一個具有指定初始容量的空列表。
@param initialCapacity 列表的初始容量
@throws IllegalArgumentException 如果指定的初始容量為負,則聲明非法數據異常
public ArrayList(int initialCapacity) { // 該構造方法指定初始容量
if (initialCapacity > 0) { // 如果初始容量>0 則 elementData.size() = 初始容量
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) { // 如果初始容量=0 則elementData.size() = 0 (EMPTY_ELEMENTDATA )
this.elementData = EMPTY_ELEMENTDATA;
} else { // 如果初始化容量<0 則拋出異常:非法數據異常
throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
}
}
// 該構造方法指定初始化容量,只是為ArrayList分配足夠的記憶體空間來存儲元素,避免在添加大量元素時頻繁進行擴容操作,指定初始化容量有助提高性能和效率,初始化大小並不表示列表中已經有了元素。
2. 構造一個初始容量為10的空列表。
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 該構造方法不指定初始容量,而是在add()添加的時候,會判斷是否使用的DEFAULTCAPACITY_EMPTY_ELEMENTDATA,如果是,則返回 DEFAULT_CAPACITY 的值。
// 也就是說,該構造方法,預設的初始容量為0,在調用add() 方法後size() 會擴張為10;
3. 構造一個包含指定集合元素的列表,按照集合迭代器返回的順序排列。
@param c 要放入此列表中的元素所屬的集合
@throws NullPointerException 如果指定的集合為null。
public ArrayList(Collection<? extends E> c) { // 該類的構造方法指定一個 Collection<E> 集合.
Object[] a = c.toArray(); // 返回 c 集合中所有元素的數組,使用變數a接收;
if ((size = a.length) != 0) { // 將a的長度賦值給 size,並判斷是否不等於0,如果為true,則執行下麵
if (c.getClass() == ArrayList.class) { // 判斷類是否相同;
elementData = a; // 相同則將傳參的地址引用賦值給 elementData ;
} else { // 不同則使用 Arrays.copy() 工具類將 a 的元素拷貝到新的 Object[] 中,並賦值給 elementData ;
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
// replace with empty array. // 如果 size() 的長度為0,則轉為空數據,EMPTY_ELEMENTDATA
elementData = EMPTY_ELEMENTDATA;
}
}
// 該構造方法指定傳入Collection集合,並獲取集合內的元素引用地址。如果傳入Collection.size() == 0,則轉為空數據,如果Collection.size() != 0,則賦值元素的引用地址。
常用方法
1. add(E e) 將指定的元素追加到此列表的末尾。
@param e 要追加到該列表的元素
@return <tt>true</tt>(與{@link Collection#add}指定的一樣)
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!! // 擴容機制判斷
elementData[size++] = e;
return true;
}
// 該方法用於ArrayList,添加新元素。
// ensureCapacityInternal(size + 1); 該方法用於判斷及執行擴容。
// elementData[size++] = e ; 數組形式賦值新元素。
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
// 該方法內部含私有方法,用於判斷容量臨界點及擴容。
// alculateCapacity(Object[] elementData, int minCapacity) 該方法用於動態標記當前最小容量。
// ensureExplicitCapacity(int minCapacity) 該方法用於判斷是否達到最小容器臨界點,是否需要擴容。
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
// 該方法用於動態標記最小容量,第一次add(),會將容量標記為10,第二次add(),會將容量標記為2 ... 依次類推,標記容量。
// 參數elementData表示當前ArrayList實例的存儲數組
// 參數minCapacity表示的最小容量。
// 如果elementData為預設的空數組(DEFAULTCAPACITY_EMPTY_ELEMENTDATA),即ArrayList實例沒有初始化過,則返回預設容量DEFAULT_CAPACITY和minCapacity中較大的那個值作為容量;否則,直接返回minCapacity作為容量。
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0) //
grow(minCapacity);
}
// 該方法判斷是否到達臨界點,如果是則擴容
// modCount:繼承自AbstractList的成員變數, 作用是修改計數,在每次add(E e) 或者 remove() 時候 modCount++ ,是為了實現"fail-fast"機制而存在的;
// 參數 minCapacity 表示ArrayList需要的最小容量
// 參數 elementData.length表示當前數組的容量
// 條件判斷minCapacity - elementData.length > 0,即表示需要的最小容量大於當前數組容量,才需要進行擴容操作。
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
// 該方法用於擴容給ArrayList擴容。
// (oldCapacity >> 1 是一個位運算符,表示將oldCapacity右移一位,即將二進位表示的oldCapacity向右移動一位,相當於將oldCapacity除以2取整數部分(捨棄小數部分)。
簡單來說,oldCapacity >> 1就是將舊容量除以2,捨棄小數部分,得到的新容量。 int newCapacity = oldCapacity + (oldCapaciyt/2);即容量擴大為原來的1.5倍。
// 如果新容量比最小容量還要小,那麼將 newCapacity 設為 minCapacity,以確保容量不會小於所需最小值。
// MAX_ARRAY_SIZE : MAX_ARRAY_SIZE 可以被認為是一個上限值,表示數組的最大容量,其值為:Integer.MAX_VALUE - 8;。
// 如果 newCapacity 超過了這個上限,就調用 hugeCapacity(minCapacity) 方法來計算一個更大的容量,並將 newCapacity 設為計算得到的值。
// 調用Arrays.copyOf工具類,其內部調用的是System.arraycopy( ),用戶將源數組拷貝到新數組中。
// 其擴容方式也就是說new一個新的Object[] ,指定其數組容量,並拷貝其中所有內容。因此每次在擴容期間,會有損性能。
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
// 方法用於計算和返回一個足夠大的容量值,以滿足所需的最小容量
// 如果 最小容量 < 0 ,則拋出 "請求的數組大小超出了虛擬機限制" 異常;
// 三元運算符,最小容量是否大於MAX_ARRAY_SIZE,如果大於則返回 Integer.MAX_VALUE,如果小於則返回 MAX_ARRAY_SIZE;
總結:add(E e) 將指定的元素追加到此列表的末尾,此方法在執行後會調用 ensureCapacityInternal()方法,用來動態標記當前執行對象的最小容量,若elementData達到最小容量(minCapacity),會在通過grow()方法,擴容1.5倍。擴容的操作是使用Arrays.copyOf工具類拷貝到一個new Object[] 數組中。因此每次擴容期間,會有損性能,每次擴容都會將原數組元素拷貝到新的數組中。
2. add(int index, E element) 在列表的指定位置插入指定的元素。將當前位置上的元素(如果有)以及後續的元素都向右移動(索引加一)。
@param index 要插入指定元素的索引位置
@param element 要插入的元素
@throws IndexOutOfBoundsException {@inheritDoc} 拋出索引越界異常
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1, size - index);
elementData[index] = element;
size++;
}
// rangeCheckForAdd(index); 該方法主要用於檢查索引範圍是否有效。
// ensureCapacityInternal(size + 1); 此方法與add(E e)中重覆,故不在詳情描述。該方法是用於判斷容量臨界點及擴容。
// System.arraycopy() 用於數組拷貝的方法。它允許將一個源數組的特定範圍的元素複製到目標數組的特定位置。
// elementData[index] = element; 指定數組的索引位置,添加元素
// size++; 元素數量+1;
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
// 該方法主要用於檢查索引範圍是否有效。
// 如果指定插入元素的位置大於當前元素的長度或者小於0,則會拋出索引越界異常。
3. addAll(Collection<? extends E> c) 將指定集合中的所有元素添加到當前集合中。
* 將指定集合中的所有元素按照其迭代器返回的順序追加到此列表的末尾。如果在操作正在進行時修改了指定集合,則此操作的行為是未定義的(這意味著如果指定的集合是此列表且此列表非空,則此調用的行為是未定義的)。
@param c 包含要添加到此列表的元素的集合
@return 如果此列表由於調用而發生更改,則返回<tt>true</tt>
@throws NullPointerException 如果指定的集合為空
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray(); // 轉換為Object[]
int numNew = a.length; // 獲取數組的長度
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew; // 計算集合的元素長度
return numNew != 0; // 如果傳入的集合為空元素,則返回fasle,如果有元素,則返回true
}
// ensureCapacityInternal(size + 1); 此方法與add(E e)中重覆,故不在詳情描述。該方法是用於判斷容量臨界點及擴容。
// System.arraycopy() 用於數組拷貝的方法。它允許將一個源數組的特定範圍的元素複製到目標數組的特定位置。
4. indexOf(Object o) 查找指定元素在列表中第一次出現的索引位置。
// ArrayList是允許null值的,所以在邏輯判斷中,需要判斷是否為null;
// 此處先判斷 傳參是否 == null ,如果為null的情況下,則查詢數組中該元素的索引位置,若有則返回該索引的位置,若無則返回 -1;如果傳參不為null, 則進入else中 使用 equals() 方法獲取對象值,若有則返回該索引的位置,若無則返回 -1;
// 此處使用 if(o==null) {} 做比較,是因為 null 值不能使用equals() 作比較。
5. lastIndexOf(Object o) 方法用於返回指定元素在列表中最後一次出現的索引位置,如果列表不包含該元素,則返回-1。
// 此方法是使用發 forr 倒序索引查詢元素是否存在,方法與上方 indexOf(Object o) 基本相同。
6. contains(Object o) 用於判斷是否包含指定的元素
* 如果此列表包含指定的元素,則返回true。更正式地說,當且僅當此列表至少包含一個元素e滿足(o==null ? e==null : o.equals(e))時,返回true。
@param o 要測試其在此列表中是否存在的元素
@return 如果此列表包含指定的元素,則返回true
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
// 該方法調用了indexOf(Object o) ,如果使用indexOf() 查詢有內容,返回true,如果沒有返回false;
7. ensureCapacity(int minCapacity) 指定最小容量,即指定elementData的長度。
* 如果需要的話,增加此 ArrayList 實例的容量,以確保它至少可以容納指定的最小容量參數指定的元素數量。
@param minCapacity 期望的最小容量
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) ? 0 : DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
// 首先判斷elementData 是不是空數據,是空數據,則 minExpand = 0 ,不是空數據,則minExpand = 10;
// 在比較 minCapacity 是否大於 minExpand ,如果大於,則執行 ensureExplicitCapacity 進行判斷是否需要擴容。
// ensureCapacityInternal(size + 1); 此方法與add(E e)中重覆,故不在詳情描述。該方法是用於判斷容量臨界點及擴容。
// 此方法與構造方法 ArrayList(int initialCapacity) 意思相同,指定其最小容量的大小。
8. trimToSize() 用於將 ArrayList 實例的容量調整為列表的當前大小
· public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size);
}
}
// modCount++:這行代碼用於增加 ArrayList 的修改計數器,以便在併發操作時能夠檢測到結構上的修改;
// if (size < elementData.length):此條件判斷當前元素的數量是否小於內部數組的長度,即判斷是否存在多餘的空間。如果存在多餘的空間,則進行下一步;
// (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size):這行代碼通過三元運算符判斷當前列表的大小是否為 0。如果大小為 0,則將 elementData 設置為空數組(EMPTY_ELEMENTDATA)。否則,使用 Arrays.copyOf() 方法將 elementData 數組複製到一個新的數組中,並將新數組的大小設置為當前元素的數量 size。
9. get(int index) 方法用於返回列表中指定位置的元素。
@param index - 要返回的元素的索引
@return - 列表中指定位置的元素
@throws IndexOutOfBoundsException {@inheritDoc} - 如果索引超出了列表的範圍
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
// 判斷 index 是否超出元素長度。
// 返回該索引所在位置的元素,註意此處是直接使用 Object[],索引應該是從 0 開始。
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
// 該方法用於檢查索引是否超出元素長度。
// 如果超出長度,則拋出索引越界異常。
10. set(int index, E element) 用指定的元素替換列表中指定位置的元素。
@param index - 要替換的元素的索引。
@param element - 要存儲在指定位置的元素。
@return - 先前位於指定位置的元素。
@throws IndexOutOfBoundsException {@inheritDoc} - 如果索引超出了列表的範圍。
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
// rangeCheck(index); 該方法用於檢查索引是否超出元素長度。
// elementData(index); 查詢該索引所在位置的元素,並賦值給 oldValue ,E 為實例
// elementData[index] = element; 替換index所在位置的元素,
// 返回已被替換的值,註意是 oldValue,是已經被替換掉的值,所以如果需要獲取替換的值,可以使用get() 方法,或遍歷。
11. toArray() 方法用於將 ArrayList 對象轉換為數組,返回Object[]
@return 包含此列表中所有元素的數組,按照正確的順序(從第一個元素到最後一個元素)
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
// 此方法使用Arrays.copyOf 方法,創建一個新的數組,並將原始數組的元素複製到新數組中,返回Object[] 。
12. toArray(T[] a) toArray()方法的重載,可指定目標數組的類型、設置新元素的預設值等
* 返回一個包含列表中所有元素按正確順序排列的數組;返回的數組的運行時類型與指定數組的運行時類型相同。如果列表適合指定的數組,則將列表複製到該數組中並返回。否則,將使用指定數組的運行時類型和列表的大小分配一個新數組。
如果指定的數組可以容納列表並有多餘空間(即數組的長度大於列表的長度),則在集合末尾的下一個位置設置為 null。(僅當調用者知道列表不包含任何 null 元素時,此功能對確定列表長度很有用)
@param:要將 a 列表元素存儲到其中的數組,如果足夠大,則直接使用;否則會為此目的分配一個相同運行時類型的新數組。
@return:包含列表元素的數組。
@throws ArrayStoreException:如果指定數組的運行時類型不是列表中每個元素的超類型。
@throws NullPointerException:如果指定的數組為 null。
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
if (a.length < size)
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
// 在 Java 的泛型(Generic)中,編譯器會進行類型檢查以確保代碼的類型安全性,而使用 @SuppressWarnings("unchecked") 可以過濾帶有泛型參數,不受檢查方法時導致的警告。
// 傳參為T[] a ,代表可傳入任意類型數組對象,且返回值<T>T[] 代表返回該類型的指定類數組。
// 如果傳入的數組 a 大小不足以容納列表元素,則創建一個新的數組,並將列表的元素拷貝到新數組中,並返回新數組。這裡使用了 Arrays.copyOf() 方法來創建新的數組,該方法會根據指定類型和長度複製原數組的內容。
// 如果傳入的數組 a 大小足夠容納列表元素,則通過 System.arraycopy() 方法將列表中的元素複製到傳入的數組 a 中。該方法可以實現高效的數組拷貝。
// 如果傳入的數組 a 大於列表的大小,則將傳入數組的第 size 個位置設置為 null,以標記後續位置為空。
// 需要註意的是,代碼中的 (T[]) 是強制類型轉換,用於將 Object[] 類型的數組轉換為泛型類型 T[]。在類型擦除的情況下,這種類型轉換可能引發 ClassCastException 異常。
13. remove(int index) 根據索引刪除對應元素
@param index 要刪除的元素的索引
@return 從列表中被刪除的元素
@throws IndexOutOfBoundsException {@inheritDoc}:如果索引越界,則拋出IndexOutOfBoundsException異常
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null;
}
// rangeCheck(index); 方法同上,用於檢查索引是否超出元素長度。
// 獲取elementData中索引位置的元素,並賦值給oldValue,numMoved 獲取當前索引位置開始到末尾還有多少個元素 ;如果numMoved大於0,表示在索引之後還有元素,此時通過System.arraycopy()方法將索引之後的所有元素向前移動一位,填補掉被刪除的元素的位置。之後,將最後一個元素置為null,並且將List的大小減1;如果numMoved小於0,則直接賦值為空數據。
// 返回值為被刪除的元素 oldValue 。
14. remove(Object o) 把指定元素從列表中移除,僅移除第一次出現的元素。
* 把指定元素從列表中移除,僅移除第一次出現的元素。如果列表不包含該元素,列表保持不變。
@param o 要從列表中移除的元素(如果存在)
@return 如果列表包含指定的元素,則返回true
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
// 首先判斷傳參是否為null,若為null,則通過該for迴圈查詢該值所在的位置,並通過fastRemove(index) 進行刪除元素,減去size長度,返回 true 。若不為null,則通過equals() 方法判斷值所在的位置,在通過調用fastRemove(index) 方法,返回 true 。若傳參不在elementData數組中,將返回 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;
}
// 該方法在remove(int index) 中出現過,作用是在給定的索引位置從ArrayList中快速移除元素。當調用該方法時,它會刪除指定索引處的元素,並且通過將後續元素向前移動來填補被刪除元素的空缺,以保持ArrayList中其它元素的連續性。
15. subList(int fromIndex, int toIndex) 方法返回一個指定範圍的子列表。
* 返回一個列表的視圖,該視圖包含從指定的fromIndex(包括)開始到toIndex(不包括)結束的部分。(如果fromIndex和toIndex相等,則返回的列表為空。)返回的列表是原列表的支持,因此返回列表中的非結構性更改會反映在原列表中,反之亦然。返回的列表支持所有可選的列表操作。# 從該子表做出修改,會將原表中的數據修改,反之亦然。
* 這個方法消除了需要顯式進行範圍操作的需求(通常適用於數組)。任何期望接收一個列表的操作都可以通過傳遞一個子列表視圖來作為範圍操作。例如,下麵的慣用法可以從列表中移除一段元素: list.subList(from, to).clear(); 類似的表達方式可以用於 indexOf(Object) 和 astIndexOf(Object),並且可以將 Collections 類中的所有演算法應用於子列表。該代碼片段的含義是刪除列表中指定範圍的元素。使用subList(from, to)方法獲取指定範圍的子列表,並調用clear()方法清空子列表中的元素,從而實現刪除操作。
* 此方法返回的列表的語義將變得未定義,如果除了通過返回的列表之外,該列表(即此列表)以任何方式進行<i>結構修改</i>。(結構修改是指改變此列表的大小或以其他方式干擾它,可能導致進行中的迭代產生不正確的結果。)# "未定義"是指在特定情況下,結果無法確定或不具備明確的含義。在這個語境中,如果對返回的列表進行結構修改(改變大小或干擾列表),結果將變得未定義,即無法預測或保證那些被修改的操作會產生怎樣的效果或影響。這可能導致迭代過程產生不正確的結果,因為修改列表的結構可能使迭代過程發生錯誤。因此,建議在對返回的列表進行任何結構修改之前,仔細考慮可能的影響和副作用。
@throws IndexOutOfBoundsException(數組越界異常):當您訪問數組或集合的索引超出其有效範圍時,會拋出該異常。
@throws IllegalArgumentException(非法參數異常):當您傳遞給方法的參數不符合方法要求或無效時,會拋出該異常。
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
// 該方法返回一個指定範圍的子列表,它是通過AbstractList類的內部類SubList實現的。
// subListRangeCheck(fromIndex, toIndex, size); 用於檢查索引範圍是否有效。
// fromIndex 表示開始索引位置(包括),toIndex 表示結束索引位置(不包括) ;舉例:從0開始索引,到第4個元素 ,fromIndex = 0 ,toIndex = 5,包頭不包尾。
static void subListRangeCheck(int fromIndex, int toIndex, int size) {
if (fromIndex < 0)
throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
if (toIndex > size)
throw new IndexOutOfBoundsException("toIndex = " + toIndex);
if (fromIndex > toIndex)
throw new IllegalArgumentException("fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")");
}
// 方法用於檢查subList()方法中指定的子列表的索引範圍是否有效,以避免出現索引越界的情況。。
private class SubList extends AbstractList<E> implements RandomAccess {
private final AbstractList<E> parent;
private final int parentOffset;
private final int offset; int size;
SubList(AbstractList<E> parent, int offset, int fromIndex, int toIndex) {
this.parent = parent;
this.parentOffset = fromIndex;
this.offset = offset + fromIndex;
this.size = toIndex - fromIndex;
this.modCount = ArrayList.this.modCount;
}
// 該類是屬於ArrayList的私有內部類,使用內部類的主要原因有以下幾點:
// 封裝性:通過將子列表作為ArrayList的內部類,可以將子列表的具體實現細節隱藏在ArrayList類內部,對外部用戶而言,只需知道如何使用子列表即可,無需瞭解其具體實現。
// 內部類訪問許可權:內部類可以訪問外部類的私有成員,這意味著子列表可以直接訪問包含它的ArrayList的底層數組和其他成員變數,這樣可以方便地共用數據,避免了額外的複製或轉換操作。
// 代碼結構和可讀性:將子列表作為內部類,可以更清晰地組織代碼結構,使得代碼更易讀、易於理解和維護。用戶通過調用ArrayList的subList方法來獲取子列表,代碼邏輯更加自然和直觀。
16. sort(Comparator<? super E> c) 對列表中的元素進行排序的方法
public void sort(Comparator<? super E> c) {
final int expectedModCount = modCount;
Arrays.sort((E[]) elementData, 0, size, c);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
// 該方法中需要傳入Comparator類型比較器,作為排序條件。
// 方法中使用Arrays.sort()工具類,將元素數組elementData排序,無返回值,也就是說預設修改elemenData 數組,因此會有modCount修改次數。
// 與Collections.sort(List<T> list)方法相同,Collections工具類中支持使用list.sort(null); null值預設排序,再此處也可以使用null值預設排序,預設排序為從小到大順序。
// 也可以使用介面 Comparator<String>() {} 重寫 compare() 方法,實現自定義比較。
舉例:
預設排序:
ArrayList<Integer> integers = new ArrayList<>();
integers.add(33);
integers.add(55);
integers.add(22);
integers.add(11);
integers.sort(null);
// integers.sort(Integer::compareTo); 該傳參是compareTo比較器,也可以通過此中方式做比較,效果一樣。
System.out.println(integers);
輸出結果:
[11, 22, 33, 55]
自定義倒排序:
ArrayList<Integer> integers = new ArrayList<>();
integers.add(33);
integers.add(55);
integers.add(22);
integers.add(11);
integers.sort(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
System.out.println(integers);
輸出結果:
[55, 33, 22, 11]
17. isEmpty() 判斷集合是否為空
@return 如果此列表不包含任何元素則返回true,反之false;
public boolean isEmpty() {
return size == 0;
}
// 比較size是否大於0;
18. clear() 移除ArrayList中所有元素,使其變為空列表。
public void clear() {
modCount++;
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
// 使用for迴圈,將每個數組元素變為null;為size賦值為0;
四. 擴展
1. Collections工具類排序
使用Collections.sort()方法對ArrayList進行排序;
示例代碼:
ArrayList<Integer> integers = new ArrayList<>();
integers.add(3);
integers.add(2);
integers.add(4);
integers.add(1);
// 使用Collections工具類按照從小到大順序排序。
Collections.sort(integers);
System.out.println(integers);
輸出結果:
[1, 2, 3, 4]
反轉ArrayList:使用Collections.reverse()方法可以將ArrayList中的元素順序反轉;
示例代碼:
ArrayList<Integer> integers = new ArrayList<>();
integers.add(3);
integers.add(2);
integers.add(4);
integers.add(1);
System.out.println("原順序:"+integers);
// 使用Collections工具類反轉排序。
Collections.reverse(integers);
System.out.println("反轉順序:"+integers);
輸出結果:
原順序:[3, 2, 4, 1]
反轉順序:[1, 4, 2, 3]
隨機打亂ArrayList:使用Collections.shuffle()方法可以隨機打亂ArrayList中的元素順序,每次查詢排序結果不一致。
示例代碼:
ArrayList<Integer> integers = new ArrayList<>();
integers.add(3);
integers.add(2);
integers.add(4);
integers.add(1);
System.out.println("原順序:"+integers);
// 使用Collections工具類隨機打亂排序。
Collections.shuffle(integers);
System.out.println("反轉順序:"+integers);
輸出結果:
原順序:[3, 2, 4, 1]
反轉順序:[2, 3, 1, 4]
2. Collections工具類二分查找 binarySearch(List<? extends Comparable<? super T>> list, T key)
list:已排序列表
key:要查找的元素
使用該方法時,需要保證列表已按照升序進行排序,並且元素類型實現了Comparable介面或者其超類實現了Comparable介面。
示例代碼:
ArrayList<Integer> integers = new ArrayList<>();
integers.add(3);
integers.add(2);
integers.add(4);
integers.add(1);
Collections.sort(integers);
int i = Collections.binarySearch(integers, 2);
System.out.println("元素所在位置:"+i);
輸出結果:
元素所在位置:1
3. Collections工具類替換指定元素,replaceAll(List<T> list, T oldVal, T newVal)
list:是要進行替換操作的列表;
oldVal:是需要被替換的舊元素;
newVal:是替換後的新元素;
該方法會遍歷列表,將所有與舊元素匹配的元素替換為新元素。如果替換操作成功進行,則返回true;否則返回false。
示例代碼:
ArrayList<Integer> integers = new ArrayList<>();
integers.add(3);
integers.add(2);
integers.add(4);
integers.add(1);
System.out.println("原list:"+integers);
boolean b = Collections.replaceAll(integers, 2, 5);