雖然 Java 中允許在運(yùn)行時確定數(shù)組的大小。
?int size = ...;
?String[] staff = new String[size];
但是并沒有完全解決運(yùn)行時動態(tài)更改數(shù)組的問題嗤练。
一旦確定了數(shù)組的大小,就不能很容易地改變它立砸。在 Java 中番甩,解決這個問題最簡單的方法是使用 Java 中另外一個類咆蒿,名為 ArrayList堪遂。ArrayList 類類似于數(shù)組尉尾,但在添加或刪除元素時爆阶,它能夠自動地調(diào)整數(shù)組容量,而不要為此編寫任何代碼沙咏。
ArrayList 是一個有類型參數(shù)(type parameter) 的泛型類(generic class)辨图。為了指定數(shù)組列表保存的元素對象的類型,需要用一對尖括號將類名括起來追加到 ArrayList 后面肢藐,例如 ArrayList<String>
故河。
1. 聲明數(shù)組列表
聲明和構(gòu)造一個保存 String 對象的數(shù)組列表:
?ArrayList<String> list = new ArrayList<String>();
在 Java 10 中,最好使用 var 關(guān)鍵字以避免重復(fù)寫類名:
?var list = new ArrayList<String>();
如果沒有使用 var 關(guān)鍵字吆豹,可以省略右邊的類型參數(shù):
?ArrayList<String> list = new ArrayList<>();
這稱為 “菱形” 語法鱼的,因為空尖括號 <>
就像是一個菱形理盆。可以結(jié)合 new 操作符使用菱形語法凑阶。編譯器會檢查新值要什么猿规。如果賦值給一個變量,或傳遞到某個方法宙橱,或者從某個方法返回姨俩,編譯器會檢査這個變量、參數(shù)或方法的泛型類型师郑,然后將這個類型放在 <>
中环葵。在這個例子中,new ArrayList<>()
將賦至一個類型為 ArrayList<String>
的變量宝冕,所以泛型類型為 String
警告: 如果使用 var 聲明 ArrayList张遭,就不能使用菱形語法。以下聲明:
?var list = new ArrayList<>();
會生成一個 ArrayList<Object>
猬仁。
注釋: Java 5 以前的版本沒有提供泛型類帝璧,而是有一個保存 Object 類型元素的 ArrayList 類,它是一個“自適應(yīng)大小”(one-size-fits-all)的集合湿刽。仍然可以使用沒有后綴 <...>
的 ArrayList,這將被認(rèn)為是刪去了類型參數(shù)的一個“原始”類型褐耳。
注釋: 在 Java 的老版本中诈闺,程序員使用 Vector 類實現(xiàn)動態(tài)數(shù)組。不過铃芦,ArrayList 類更加高效雅镊,沒有任何理由再使用 Vector 類。
使用 add 方法可以將元素添加到數(shù)組列表中刃滓。例如仁烹,下面展示了如何將 String 對象添加到一個數(shù)組列表中:
ArrayList<String> staff = new ArrayList<String>();
staff.add(new String("Harry Hacker"));
staff.add(new String("Tommy Tester"));
數(shù)組列表管理著一個內(nèi)部的對象引用數(shù)組。最終咧虎,數(shù)組的全部空間有可能被用盡卓缰。這時就顯現(xiàn)出數(shù)組列表的魅力了:如果調(diào)用 add 且內(nèi)部數(shù)組已經(jīng)滿了,數(shù)組列表就會自動地創(chuàng)建一個更大的數(shù)組砰诵,并將所有的對象從較小的數(shù)組中拷貝到較大的數(shù)組中征唬。
如果已經(jīng)清楚或能夠估計出數(shù)組可能存儲的元素數(shù)量,就可以在填充數(shù)組之前調(diào)用 ensureCapacity 方法:
?list.ensuteCapacity(100);
這個方法調(diào)用將分配一個包含 100 個對象的內(nèi)部數(shù)組茁彭。這樣一來总寒,前 100 次 add 調(diào)用不會帶來開銷很大的重新分配空間。
還可以把初始容量傳遞給 ArrayList 構(gòu)造器:
?ArrayList<String> list = new ArrayList<>(100);
警告: 如下分配數(shù)組列表:
?new ArrayList<>(100) // capacity is 100
這與分配一個新數(shù)字有所不同:
?new String[100] // size is 100
數(shù)組列表的容量與數(shù)組的大小有一個非常重要的區(qū)別理肺。如果分配一個有 100 個元素的數(shù)組摄闸。數(shù)組就有 100 個空位置(槽)可以使用善镰。而容量為 100 個元素的數(shù)組列表只是可能保存 100 個元素(實際上也可以超過 100,不過要以重新分配空間為代價)年枕,但是在最初媳禁,甚至完成初始化構(gòu)造之后,數(shù)組列表不包含任何元素画切。
size 方法將返回數(shù)組列表中包含的實際元素個數(shù)竣稽。
?list.size()
將返回 staff 數(shù)組列表的當(dāng)前元素個數(shù),它等價于數(shù)組 a 的 a.length霍弹。
一旦能夠確認(rèn)數(shù)組列表的大小將保持恒定毫别,不再發(fā)生變化,就可以調(diào)用 trimToSize 方法典格。這個方法將存儲塊的大小調(diào)整為保存當(dāng)前元素數(shù)量所需要的存儲空間岛宦。垃圾回收器將回收多余的存儲空間。
一旦消減了數(shù)組列表的大小耍缴,添加新元素就需要花時間再次移動存儲塊砾肺,所以應(yīng)該在確認(rèn)不會再向數(shù)組列表添加任何元素時再調(diào)用 trimToSize。
java.util.ArrayList<E>
1.2
-
ArrayList<E>
構(gòu)造一個空數(shù)組列表防嗡。
- boolean add(E obj)
在數(shù)組列表的末尾追加一個元素变汪。永遠(yuǎn)返回 true。
- int size()
返回當(dāng)前存儲在數(shù)組列表中元素個數(shù)蚁趁。(當(dāng)然裙盾,這個值永遠(yuǎn)不會大于數(shù)組列表的容量)
- void ensureCapacity()
確保數(shù)組列表不重新分配內(nèi)部存儲數(shù)組的情況下有足夠的容量存儲給定數(shù)量的元素。
- void trimToSize()
將數(shù)組列表的存儲容量削減到當(dāng)前大小他嫡。
2. 訪問數(shù)組列表元素
數(shù)組列表自動擴(kuò)展容量的便利增加了訪問元素語法的復(fù)雜程度番官。其原因是 ArrayList 類并不是 Java 程序設(shè)計語言的一部分;它只是由某個人編寫并在標(biāo)準(zhǔn)庫中提供的一個實用工具類钢属。
數(shù)組列表使用 get 和 set 方法訪問或改變數(shù)組列表的元素徘熔。
警告: 只有當(dāng)前數(shù)組列表的大小大于 i 時,才能夠調(diào)用 list.set(i, x)
淆党。例如這段代碼時錯誤的:
?var list = new ArrayList<String>(100); // capacity 100, size 0
?list.set(0, x);
要使用 add 方法為數(shù)組添加新元素酷师,而不是 set 方法,set 方法只是用來替換數(shù)組列表中已經(jīng)加入的元素宁否。
要得到一個數(shù)組列表的元素窒升,使用 get 方法:
?String srt = list.get(i);
注釋: 沒有泛型時,原始的 ArrayList 類提供的 get 方法別無選擇慕匠,只能返回 Object饱须,因此,get 方法的調(diào)用者必須對返回值進(jìn)行強(qiáng)制類型轉(zhuǎn)換:
?String str = (String) list.get(i);
原始的 ArrayList 還存在一定的危險性台谊。它的 add 和 set 方法接受任意類型的對象蓉媳。對于下面這個調(diào)用:
?list.set(i, new Date());
它能正常編譯而不會給出任何警告譬挚,只有在檢索對象并試圖對它進(jìn)行強(qiáng)制類型轉(zhuǎn)換時,才會發(fā)現(xiàn)有問題酪呻。如果使用 ArrayList<String>
减宣,編譯器就會檢測到這個錯誤。
這個技巧可以一舉兩得玩荠,即可以靈活地擴(kuò)展數(shù)組漆腌,又可以方便地訪問數(shù)組列表。首先阶冈,創(chuàng)建一個數(shù)組列表闷尿,并添加所有的元素。
var list = new ArrayList<X>();
while (...) {
x = ...;
list.add(x);
}
執(zhí)行完上述操作后女坑,使用 toArray 方法將數(shù)組元素拷貝到一個數(shù)組中填具。
?var a = new X[list.size()];
?list.toArray(a);
有時需要在數(shù)組列表的中間插入元素,為此可以使用 add 方法并提供一個索引參數(shù)匆骗。
?var str = "";
?int n = staff.size() / 2;
?list.add(n, str);
位置 n 及以后的元素都要向后移動一個位置劳景,為新元素留出空間。插入新元素后碉就,如果數(shù)組列表新的大小超過了容量盟广,數(shù)組列表就會重新分配它的存儲數(shù)組。
可以從數(shù)組列表中間刪除一個元素:
?String srt = list.remove(n);
位于這個位置之后的所有元素都向前移動一位铝噩,并且數(shù)組的大小減 1衡蚂。
插入和刪除元素的操作效率很低。對于較小的數(shù)組列表來說骏庸,不必?fù)?dān)心這個問題。但如果存儲的元素比較多年叮,又經(jīng)常需要在中間插入具被、刪除元素,就應(yīng)該考慮使用鏈表了只损。
可以使用 “for each” 循環(huán)遍歷數(shù)組列表的內(nèi)容:
for (String str : list) {
do something with str
}
這個循環(huán)和下列代碼具有相同的效果:
for (int i = 0; i < list.size(); i++) {
String str = list.get(i);
do something with str
}
使用數(shù)組列表情注意下面的變化:
- 不必指定數(shù)組列表的大小一姿。
- 使用 add 將任意多的元素添加到數(shù)組列表中。
- 使用 size() 而不是 length 統(tǒng)計元素個數(shù)跃惫。
- 使用 list.get(i) 而不是 list[i] 來訪問元素叮叹。
java.util.ArrayList<E> 1.2
- E set(int index, E obj)
將值 obj 放置在數(shù)組列表的指定索引位置,返回之前的內(nèi)容爆存。
- E get(i)
得到指定索引位置存儲的值蛉顽。
- void add(int index, E obj)
后移元素從而將 obj 插入到指定索引位置。
- E remove(int index)
刪除指定索引位置的元素先较,并將后面的所有元素前移携冤。返回所刪除的元素悼粮。
3. 類型化與原始數(shù)組列表的兼容性
假設(shè)有下面這個遺留下來的類:
public class EmployeeDB
{
public void update(ArrayList list) { . . . }
public ArrayList find(String query) { . . . }
}
public class Employee {
private String name;
private double salary;
private LocalDate hireDay;
public Employee(String name, double salary, int year, int month, int day) {
this.name = name;
this.salary = salary;
hireDay = LocalDate.of(year, month, day);
}
}
可以將一個類型化的數(shù)組列表傳遞給 update 方法,而并不需要進(jìn)行任何類型轉(zhuǎn)換曾棕。
EmployeeDB employeeDB = new employeeDB();
ArrayList<Employee> list = . . .;
employeeDB.update(list);
可以將 list 對象傳遞給 update方法扣猫。
相反地,將一個原始 ArrayList 賦給一個類型化 ArrayList 會得到一個警告翘地。
?ArrayList<String> result = employeeDB.find(query); // yields warning
注釋: 為了能夠看到警告的文字信息申尤,要將編譯選項置為 -Xlint:unchecked
。
使用強(qiáng)制類型轉(zhuǎn)換并不能避免出現(xiàn)警告衙耕。
?ArrayList<String> result = (ArrayList<Employee>) employeeDB.find(query);
??// yields another warning
這樣將會得到另外一個警告信息昧穿,指出類型轉(zhuǎn)換有誤。
警告: 盡管編譯器沒有給出任何錯誤信息或警告臭杰,但是這樣調(diào)用并不太安全粤咪。在 update 方法中,添加到數(shù)組列表中的元素可能不是 Employee 類型渴杆。訪問這些元素時就會出現(xiàn)異常寥枝。 聽起來似乎很嚇人,但思考一下就會發(fā)現(xiàn)磁奖,這種行為與 Java 中引入泛型之前是一樣的囊拜,虛擬機(jī)的完整性并沒有受到威脅。在這種情形下比搭,既沒有降低安全性冠跷,也沒有受益于編譯時的檢查。
這就是 Java 中不盡如人意的泛型類型限制所帶來的結(jié)果身诺。出于兼容性的考慮蜜托,編譯器檢查到?jīng)]有發(fā)現(xiàn)違反規(guī)則的現(xiàn)象之后,就將所有的類型化參數(shù)列表轉(zhuǎn)換成原始 ArrayList 對象霉赡。在程序運(yùn)行時橄务,所有的數(shù)組列表都是一樣的,即虛擬機(jī)中沒有類型參數(shù)穴亏。因此蜂挪,強(qiáng)制類型轉(zhuǎn)換(ArrayList)和(ArrayList<Employee>
)將執(zhí)行相同的運(yùn)行時檢查。
在這種情形下嗓化,你并不能做什么棠涮。在與遺留的代碼交互時,要研究編譯器的警告刺覆,確保這些警告不太嚴(yán)重就行了严肪。
一旦確保問題不太嚴(yán)重,可以用 @SuppressWamings("unchecked") 注解來標(biāo)記接受強(qiáng)制類型轉(zhuǎn)換的變量,如下所示:
@SuppressWarnings("unchecked") ArrayList<String> result =
(ArrayList<String>) employeeDB.find(query); // yields another warning