Java集合框架由Java類庫的一系列接口、抽象類以及具體實(shí)現(xiàn)類組成。集合就是把一組對象組織到一起,然后再根據(jù)不同的需求操縱這些數(shù)據(jù)。集合類型就是容納這些對象的一個容器。根據(jù)集合中是否允許有重復(fù)的對象、對象組織在一起是否按某種順序等標(biāo)準(zhǔn)來劃分的話,集合類型又可以細(xì)分為許多種不同的子類型。
java集合框架提供了一組基本機(jī)制以及這些機(jī)制的參考實(shí)現(xiàn),其中基本的集合接口是Collection接口,其他相關(guān)的接口還有Iterator接口、RandomAccess接口。
抽象類的好處:提供了接口的部分實(shí)現(xiàn),這樣就可以在實(shí)現(xiàn)類的基礎(chǔ)上實(shí)現(xiàn)部分功能而不必重寫接口的所有方法。
Collection接口
Collection接口是集合層級結(jié)構(gòu)的根接口,里面的方法有:
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
Object[] toArray();
<T> T[] toArray(T[] a);
boolean add(E e);
boolean remove(Object o);
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean removeAll(Collection<?> c);
boolean retainAll(Collection<?> c); //僅保留給定集合c中的元素(optional operation).
void clear();//清空集合
boolean equals(Object o);//繼承自O(shè)bject
int hashCode();//繼承自O(shè)bject
值得注意的是兩個toArray方法:
它們的功能都是都是返回這個集合的對象數(shù)組。第二個方法接收一個arrayToFill參數(shù),當(dāng)這個參數(shù)數(shù)組足夠大時,就把集合中的元素都填入這個數(shù)組(多余空間填null);當(dāng)arrayToFill不夠大時,就會創(chuàng)建一個大小與集合相同,類型與arrayToFill相同的數(shù)組,并填入集合元素。
我們看一下Collection<E>接口的迭代器:
public interface Iterable<T> {
Iterator<T> iterator();
}
這個接口只定義了一個方法,這個方法要求我們返回一個實(shí)現(xiàn)了Iterator<T>類型的對象,所以我們看下Iterator<T>的定義:
public interface Iterator<E> {
boolean hasNext();
E next();
void remove();
}
迭代器就是一個我們用來遍歷集合中的對象的東西。即對于集合,我們不像對原始類型數(shù)組那樣通過數(shù)組索引來直接訪問相應(yīng)位置的元素,而是通過迭代器來遍歷。這么做的好處是將對于集合類型的遍歷行為與被遍歷的集合對象分離,這樣一來我們無需關(guān)心該集合類型的具體實(shí)現(xiàn)是怎樣的。只要獲取這個集合對象的迭代器, 便可以遍歷這個集合中的對象了。而像遍歷對象的順序這些細(xì)節(jié),全部由它的迭代器來處理。
小總結(jié):Collection接口實(shí)現(xiàn)了Iterable<E>接口,這意味著所有實(shí)現(xiàn)了Collection接口的具體集合類都是可迭代的。一個迭代器對象也就是實(shí)現(xiàn)了Iterator<E>接口的對象,這個接口要求我們實(shí)現(xiàn)hasNext()、next()、remove()這三個方法。通常,迭代一個集合對象的代碼是這個樣子的:
Collection<String> c = ...;
Iterator<String> iter = c.iterator();
while (iter.hasNext()) {
String element = iter.next();
//do something with element
}
以上代碼可以用增強(qiáng)for來代替:
for (String element : c) {
//do something with element
}
注意:Iterator接口的remove方法必須在next方法返回一個元素后才能調(diào)用!
Collection接口是集合層級結(jié)構(gòu)的根接口。一個集合代表了一組對象,這組對象被稱為集合的元素。一些集合允許重復(fù)的元素而其他不允許;一些是有序的而一些是無序的。Java類庫中并未提供任何對這個接口的直接實(shí)現(xiàn),而是提供了對于它的更具體的子接口的實(shí)現(xiàn)(比如Set接口和List接口)。
Collection接口的直接子接口主要有三個:List接口、Set接口和Queue接口。
- List接口:
List是一個有序的集合類型(也被稱作序列)。使用List接口可以精確控制每個元素被插入的位置,并且可以通過元素在列表中的索引來訪問它。列表允許重復(fù)的元素,并且在允許null元素的情況下也允許多個null元素。
官方定義了以下這些方法:
ListIterator<E> listIterator();
void add(int i, E element);
E remove(int i);
E get(int i);
E set(int i, E element);
int indexOf(Object element);
上面有一個listIterator方法,它返回一個列表迭代器。ListIterator<E>接口定義的方法有:
void add(E e) //在當(dāng)前位置添加一個元素
boolean hasNext() //返回ture如果還有下個元素(在正向遍歷列表時使用)
boolean hasPrevious() //反向遍歷列表時使用
E next() //返回下一個元素并將cursor(也就是指針)前移一個位置
int nextIndex() //返回下一次調(diào)用next方法將返回的元素的索引
E previous() //返回前一個元素并將cursor向前移動一個位置
int previousIndex() //返回下一次調(diào)用previous方法將返回的元素的索引void remove() //從列表中移除最近一次調(diào)用next方法或previous方法返回的元素
void set(E e) //用e替換最近依次調(diào)用next或previous方法返回的元素
ListIterator<E>是Iterator<E>的子接口,它支持像雙向迭代這樣更加特殊化的操作。
Java類庫中常見的實(shí)現(xiàn)了List<E>接口的類有:ArrayList, LinkedList,Stack,Vector,AbstractList,AbstractSequentialList等等。
- ArrayList
ArrayList是一個可動態(tài)調(diào)整大小的數(shù)組,允許null類型的元素。Java中的數(shù)組大小在初始化時就必須確定下來,而且一旦確定就不能改變,這會使得在很多場景下不夠靈活。ArrayList很好地幫我們解決了這個問題,當(dāng)我們需要一個能根據(jù)包含元素的多少來動態(tài)調(diào)整大小的數(shù)組時,那么ArrayList正是我們所需要的。常用方法有:
boolean add(E e) //添加一個元素到數(shù)組末尾
void add(int index, E element) //添加一個元素到指定位置
void clear()
boolean contains(Object o)
void ensureCapacity(int minCapacity) //確保ArrayList至少能容納參數(shù)指定數(shù)目的對象,若有需要會增加ArrayList實(shí)例的容量。
E get(int index) //返回指定位置的元素
int indexOf(Object o)
boolean isEmpty()
Iterator<E> iterator()
ListIterator<E> listIterator()
E remove(int index)
boolean remove(Object o)
E set(int index, E element)
int size()
當(dāng)我們插入了比較多的元素,導(dǎo)致ArrayList快要裝滿時,它會自動增長容量。ArrayList內(nèi)部使用一個Object數(shù)組來存儲元素,自動增長容量是通過創(chuàng)建一個新的容量更大的Object數(shù)組,并將元素從原Object數(shù)組復(fù)制到新Object數(shù)組來實(shí)現(xiàn)的。若要想避免這種開銷,在知道大概會容納多少數(shù)據(jù)時,我們可以在構(gòu)造時指定好它的大小以盡量避免它自動增長的發(fā)生;我們也可以調(diào)用ensureCapacity方法來增加ArrayList對象的容量到我們指定的大小。ArrayList有以下三個構(gòu)造器:
ArrayList()
ArrayList(Collection<? extends E> c)
ArrayList(int initialCapacity) //指定初始capacity,即內(nèi)部Object數(shù)組的初始大小
這里提一下Arraylist的值得注意的一點(diǎn),集合成員允許出現(xiàn)null元素,而且可以多個。請看一下代碼:
/**
* 關(guān)于List中是否可以添加Null的測試
* @author kyy
*/
public class Demo1 {
public static void main(String[] args) {
List<User> users=new ArrayList<User>();
for (int i = 0; i < 10; i++) {
users.add(new User());
}
//添加null不報(bào)錯,但是這樣在堆集合元素進(jìn)行方法調(diào)用時,有可能出現(xiàn)空指針異常
users.add(null);
//添加Object,報(bào)錯
//users.add(new Object());
System.out.println(users.size());//運(yùn)行結(jié)果:11
for (User user : users) {
//這里要進(jìn)行判斷,不然有可能發(fā)生空指針異常
/*if(user!=null){
System.out.println(user.getUsername());
}*/
System.out.println(user.getUsername());
}
}
}
class User{
private String username="小明";
private int age;
public String getUsername(){
return this.username;
}
}
因?yàn)锳rraylist中允許出現(xiàn)null元素,所以在遍歷的時候,如果為對象,獲取對象屬性的時候要先判斷遍歷出來的對象是否為null,這樣才可以避免空指針異常。
而且,集合不為空不代表集合長度就不為0,所以在遍歷集合的時候要進(jìn)行雙重判斷,先判斷集合是否為Null,再判斷集合是否長度大于0.
- LinkedList類
LinkedList類代表了一個雙向鏈表,也允許null元素。這個類同ArrayList一樣,不是線程安全的。
這個類中主要有以下的方法:
void addFirst(E element);
void addLast(E element);
E getFirst();
E getLast();
E removeFirst();
E removeLast();
LinkedList的一個缺陷在于它不支持對元素的高效隨機(jī)訪問,要想隨機(jī)訪問其中的元素,需要逐個掃描直到遇到符合條件的元素。只有當(dāng)我們需要減少在列表中間添加或刪除元素操作的代價時,可以考慮使用LinkedList。jdk類庫中實(shí)現(xiàn)了List<E>接口的類有:ArrayList, LinkedList,Stack,Vector,AbstractList,AbstractSequentialList等等。
說到線程安全,List接口的幾個實(shí)現(xiàn)類中,Arraylist和Linkedlist都不是線程安全的,而Stack和Vector則是線程安全的,但是線程不安全的可以通過Collections的同步方法轉(zhuǎn)換為線程安全對象,具體實(shí)現(xiàn),請讀者翻看jdk源代碼,我相信會有另一翻收獲的!
參考資料
- 《Java核心技術(shù)(卷一)》
- What is a view of a collection?
- Java SE 7 Docs