迭代器模式:就是提供一種方法對(duì)一個(gè)容器對(duì)象中的各個(gè)元素進(jìn)行訪(fǎng)問(wèn)娱节,而又不暴露該對(duì)象容器的內(nèi)部細(xì)節(jié)。
概述
Java集合框架的集合類(lèi)辐益,我們有時(shí)候稱(chēng)之為容器断傲。容器的種類(lèi)有很多種,比如ArrayList智政、LinkedList艳悔、HashSet...,每種容器都有自己的特點(diǎn)女仰,ArrayList底層維護(hù)的是一個(gè)數(shù)組猜年;LinkedList是鏈表結(jié)構(gòu)的;HashSet依賴(lài)的是哈希表疾忍,每種容器都有自己特有的數(shù)據(jù)結(jié)構(gòu)乔外。
因?yàn)槿萜鞯膬?nèi)部結(jié)構(gòu)不同,很多時(shí)候可能不知道該怎樣去遍歷一個(gè)容器中的元素一罩。所以為了使對(duì)容器內(nèi)元素的操作更為簡(jiǎn)單杨幼,Java引入了迭代器模式!
把訪(fǎng)問(wèn)邏輯從不同類(lèi)型的集合類(lèi)中抽取出來(lái)聂渊,從而避免向外部暴露集合的內(nèi)部結(jié)構(gòu)差购。
對(duì)于數(shù)組的遍歷,使用下標(biāo)完成:
int array[] = new int[3];
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
對(duì)于ArrayList的遍歷汉嗽,同樣是使用序號(hào):
List<String> list = new ArrayList<String>();
for(int i = 0 ; i < list.size() ; i++){
String string = list.get(i);
}
對(duì)于這兩種方式欲逃,我們總是都知道它的內(nèi)部結(jié)構(gòu),訪(fǎng)問(wèn)代碼和集合本身是緊密耦合的饼暑,無(wú)法將訪(fǎng)問(wèn)邏輯從集合類(lèi)和客戶(hù)端代碼中分離出來(lái)稳析。不同的集合會(huì)對(duì)應(yīng)不同的遍歷方法,客戶(hù)端代碼無(wú)法復(fù)用弓叛。在實(shí)際應(yīng)用中如何將上面兩個(gè)集合整合是相當(dāng)麻煩的彰居。所以才有Iterator,它總是用同一種邏輯來(lái)遍歷集合撰筷。使得客戶(hù)端自身不需要來(lái)維護(hù)集合的內(nèi)部結(jié)構(gòu)陈惰,所有的內(nèi)部狀態(tài)都由Iterator來(lái)維護(hù)”献眩客戶(hù)端不用直接和集合進(jìn)行打交道抬闯,而是控制Iterator向它發(fā)送向前向后的指令,就可以遍歷集合影钉。
java.util.Interator
在Java中Iterator為一個(gè)接口画髓,它只提供了迭代的基本規(guī)則。在JDK中它是這樣定義的:對(duì)Collection進(jìn)行迭代的迭代器平委。迭代器取代了Java Collection Framework中的Enumeration奈虾。迭代器與枚舉有兩點(diǎn)不同:
迭代器在迭代期間可以從集合中移除元素。
方法名得到了改進(jìn)廉赔,Enumeration的方法名稱(chēng)都比較長(zhǎng)肉微。
其接口定義如下:
package java.util;
public interface Iterator<E> {
boolean hasNext();//判斷是否存在下一個(gè)對(duì)象元素
E next();//獲取下一個(gè)元素
void remove();//移除元素
}
Interable
Java中還提供了一個(gè)Iterable接口,Iterable接口實(shí)現(xiàn)后的功能是‘返回’一個(gè)迭代器蜡塌,我們常用的實(shí)現(xiàn)了該接口的子接口有:Collection<E>碉纳、List<E>、Set<E>等馏艾。該接口的iterator()方法返回一個(gè)標(biāo)準(zhǔn)的Iterator實(shí)現(xiàn)劳曹。實(shí)現(xiàn)Iterable接口允許對(duì)象成為Foreach語(yǔ)句的目標(biāo)奴愉。就可以通過(guò)foreach語(yǔ)句來(lái)遍歷你的底層序列。
Iterable接口包含一個(gè)能產(chǎn)生Iterator對(duì)象的方法铁孵,并且Iterable被foreach用來(lái)在序列中移動(dòng)锭硼。因此如果創(chuàng)建了實(shí)現(xiàn)Iterable接口的類(lèi),都可以將它用于foreach中蜕劝。
interable接口的具體實(shí)現(xiàn):
Package java.lang;
import java.util.Iterator;
public interface Iterable<T> {
Iterator<T> iterator();
}
使用迭代器遍歷集合:
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("張三1");
list.add("張三2");
list.add("張三3");
list.add("張三4");
List<String> linkList = new LinkedList<String>();
linkList.add("link1");
linkList.add("link2");
linkList.add("link3");
linkList.add("link4");
Set<String> set = new HashSet<String>();
set.add("set1");
set.add("set2");
set.add("set3");
set.add("set4");
//使用迭代器遍歷ArrayList集合
Iterator<String> listIt = list.iterator();
while(listIt.hasNext()){
System.out.println(listIt.hasNext());
}
//使用迭代器遍歷Set集合
Iterator<String> setIt = set.iterator();
while(setIt.hasNext()){
System.out.println(listIt.hasNext());
}
//使用迭代器遍歷LinkedList集合
Iterator<String> linkIt = linkList.iterator();
while(linkIt.hasNext()){
System.out.println(listIt.hasNext());
}
}
使用foreach遍歷集合:
List<String> list = new ArrayList<String>();
list.add("張三1");
list.add("張三2");
list.add("張三3");
list.add("張三4");
for (String string : list) {
System.out.println(string);
}
可以看出使用foreach遍歷集合的優(yōu)勢(shì)在于代碼更加的簡(jiǎn)潔檀头,更不容易出錯(cuò),不用關(guān)心下標(biāo)的起始值和終止值岖沛。
Iterator遍歷時(shí)不可以刪除集合中的元素問(wèn)題
在使用Iterator的時(shí)候禁止對(duì)所遍歷的容器進(jìn)行改變其大小結(jié)構(gòu)的操作暑始。例如: 在使用Iterator進(jìn)行迭代時(shí),如果對(duì)集合進(jìn)行了add婴削、remove操作就會(huì)出現(xiàn)==ConcurrentModificationException==異常廊镜。
List<String> list = new ArrayList<String>();
list.add("張三1");
list.add("張三2");
list.add("張三3");
list.add("張三4");
//使用迭代器遍歷ArrayList集合
Iterator<String> listIt = list.iterator();
while(listIt.hasNext()){
Object obj = listIt.next();
if(obj.equals("張三3")){
list.remove(obj);
}
}
因?yàn)樵谀愕埃饕呀?jīng)被通過(guò)list.itertor()創(chuàng)建出來(lái)了馆蠕,如果在迭代的過(guò)程中期升,又對(duì)list進(jìn)行了改變其容器大小的操作,那么Java就會(huì)給出異常互躬。因?yàn)榇藭r(shí)Iterator對(duì)象已經(jīng)無(wú)法主動(dòng)同步list做出的改變播赁,Java會(huì)認(rèn)為你做出這樣的操作是線(xiàn)程不安全的,就會(huì)給出善意的提醒(拋出ConcurrentModificationException異常)
Iterator的實(shí)現(xiàn)源碼:
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
通過(guò)查看源碼發(fā)現(xiàn)原來(lái)檢查并拋出異常的是checkForComodification()方法吼渡。在ArrayList中==modCount是當(dāng)前集合的版本號(hào)==容为,每次修改(增、刪)集合都會(huì)加1寺酪;==expectedModCount是當(dāng)前迭代器的版本號(hào)==坎背,在迭代器實(shí)例化時(shí)初始化為modCount。我們看到在checkForComodification()方法中就是在驗(yàn)證modCount的值和expectedModCount的值是否相等寄雀,所以當(dāng)你在調(diào)用了ArrayList.add()或者ArrayList.remove()時(shí)得滤,只更新了modCount的狀態(tài),而迭代器中的expectedModCount未同步盒犹,因此才會(huì)導(dǎo)致再次調(diào)用Iterator.next()方法時(shí)拋出異常懂更。但是為什么使用Iterator.remove()就沒(méi)有問(wèn)題呢?通過(guò)源碼的第32行發(fā)現(xiàn)急膀,在Iterator的remove()中同步了expectedModCount的值沮协,所以當(dāng)你下次再調(diào)用next()的時(shí)候,檢查不會(huì)拋出異常卓嫂。
使用該機(jī)制的主要目的是為了實(shí)現(xiàn)ArrayList中的快速失敗機(jī)制(fail-fast)慷暂,在Java集合中較大一部分集合是存在快速失敗機(jī)制的。
==快速失敗機(jī)制產(chǎn)生的條件:當(dāng)多個(gè)線(xiàn)程對(duì)Collection進(jìn)行操作時(shí)晨雳,若其中某一個(gè)線(xiàn)程通過(guò)Iterator遍歷集合時(shí)行瑞,該集合的內(nèi)容被其他線(xiàn)程所改變奸腺,則會(huì)拋出ConcurrentModificationException異常。==
所以要保證在使用Iterator遍歷集合的時(shí)候不出錯(cuò)誤蘑辑,就應(yīng)該保證在遍歷集合的過(guò)程中不會(huì)對(duì)集合產(chǎn)生結(jié)構(gòu)上的修改洋机。
使用Foreach時(shí)對(duì)集合的結(jié)構(gòu)進(jìn)行修改會(huì)出現(xiàn)異常:
上面我們說(shuō)了實(shí)現(xiàn)了Iterable接口的類(lèi)就可以通過(guò)Foreach遍歷,那是因?yàn)閒oreach要依賴(lài)于Iterable接口返回的Iterator對(duì)象洋魂,所以從本質(zhì)上來(lái)講,F(xiàn)oreach其實(shí)就是在使用迭代器喜鼓,在使用foreach遍歷時(shí)對(duì)集合的結(jié)構(gòu)進(jìn)行修改副砍,和在使用Iterator遍歷時(shí)對(duì)集合結(jié)構(gòu)進(jìn)行修改本質(zhì)上是一樣的。所以同樣的也會(huì)拋出異常庄岖,執(zhí)行快速失敗機(jī)制豁翎。
foreach是JDK1.5新增加的一個(gè)循環(huán)結(jié)構(gòu),foreach的出現(xiàn)是為了簡(jiǎn)化我們遍歷集合的行為隅忿。
for循環(huán)與迭代器的對(duì)比:
- 效率上各有各的優(yōu)勢(shì):
ArrayList對(duì)隨機(jī)訪(fǎng)問(wèn)比較快心剥,而for循環(huán)中使用的get()方法,采用的即是隨機(jī)訪(fǎng)問(wèn)的方法背桐,因此在ArrayList里for循環(huán)快优烧。
LinkedList則是順序訪(fǎng)問(wèn)比較快,Iterator中的next()方法采用的是順序訪(fǎng)問(wèn)方法链峭,因此在LinkedList里使用Iterator較快畦娄。
主要還是要依據(jù)集合的數(shù)據(jù)結(jié)構(gòu)不同的判斷。