java 中 Iterator 和 Iterable 區別
Iterator(迭代器)
作爲一種設計模式,迭代器可以用於遍歷一個對象,而開發人員不用去了解這個對象的底層結構。這裏就不仔細說迭代器這種設計模式,因爲我們主要的目的是探索java中Iterator和 Iterable之間的區別
用法
首先來說一下他們各自是怎麼使用,不會用談什麼都是瞎搞。
Iterator用法
首先來說一下Iterator這個接口,他定義了迭代器基本的功能。
源碼如下
package java.util; public interface Iterator<E> { boolean hasNext(); //返回是否有下一個元素 E next(); //返回下一個元素 void remove(); //移除當前元素 }
如何使用這個接口
public class main { public static void main(String[] args) { MyIterator mi = new MyIterator(); while (mi.hasNext()){ System.out.printf("%s\t",mi.next()); } } } class MyIterator implements Iterator<String>{ private String[] words = ("And that is how " + "we know the Earth to be banana-shaped.").split(" "); private int count = 0; public MyIterator() { } @Override public boolean hasNext() { return count < words.length; } @Override public String next() { return words[count++]; } }
代碼很簡單,所以就不過多的解釋了,但是有一點需要注意的是,下面這樣使用上面定義的MyIterator類是錯誤的,你可以試一下。
for (String s:new MyIterator){ System.out.printf("%s\t",s); }
Iterable用法
源碼
package java.lang; import java.util.Iterator; public interface Iterable<T> { Iterator<T> iterator(); }
如何使用
public class main { public static void main(String[] args) { MyIterable myIterable= new MyIterable(); Iterator<String> mIterator = myIterable.iterator(); for (String s:myIterable){ System.out.printf("%s\t",s); } } } class MyIterable implements Iterable<String>{ private String[] words = ("And that is how " + "we know the Earth to be banana-shaped.").split(" "); @Override public Iterator<String> iterator() { return new Iterator<String>() { private int i = 0; @Override public boolean hasNext() { return i < words.length; } @Override public String next() { return words[i++]; } }; } }
與上面Iterator不同的是,這個類還可以下面這樣使用
MyIterable mi = new MyIterable().iterator(); while (mi.hasNext()){ System.out.printf("%s\t",mi.next()); }
區別
基本用法已經說完,相信你也能看出其中的一些區別
- Iterator是迭代器類(這個類是指定義了迭代器基本需要的方法),而Iterable是接口。 這個從他們的包名就可以看出來。
java.lang.Iterable java.util.Iterator
Iterator不嫩用於foreach循環語句,Iterable可以
爲什麼一定要實現Iterable接口,爲什麼不直接實現Iterator接口呢?
看一下JDK中的集合類,比如List一族或者Set一族,都是實現了Iterable接口,但並不直接實現Iterator接口。這並不是沒有道理的。因爲Iterator接口的核心方法next()或者hasNext() 是依賴於迭代器的當前迭代位置的。如果Collection直接實現Iterator接口,勢必導致集合對象中包含當前迭代位置的數據(指針)。當集合在不同方法間被傳遞時,由於當前迭代位置不可預置,那麼next()方法的結果會變成不可預知。 除非再爲Iterator接口添加一個reset()方法,用來重置當前迭代位置。但即時這樣,Collection也只能同時存在一個當前迭代位置。而Iterable則不然,每次調用都會返回一個從頭開始計數的迭代器。多個迭代器是互不干擾的。
擴展
你在看ArrayList源碼的時候,你會發現這樣一段代碼
private class Itr implements Iterator<E> { int cursor; // 返回下一個元素的索引 int lastRet = -1; // 返回最後一個元素的索引,如果空,返回-1 int expectedModCount = modCount; //用於檢測當前集合是否執行了添加刪除操作,其中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(); } } @Override @SuppressWarnings("unchecked") public void forEachRemaining(Consumer<? super E> consumer) { Objects.requireNonNull(consumer); final int size = ArrayList.this.size; int i = cursor; if (i >= size) { return; } final Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) { throw new ConcurrentModificationException(); } while (i != size && modCount == expectedModCount) { consumer.accept((E) elementData[i++]); } // update once at end of iteration to reduce heap write traffic cursor = i; lastRet = i - 1; checkForComodification(); } //如果發生添加刪除操作,則拋出錯誤。 final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
對於上述的代碼不難看懂,有點疑惑的是int expectedModCount = modCount;這句代碼
其實這是集合迭代中的一種“快速失敗”機制,這種機制提供迭代過程中集合的安全性。閱讀源碼
就可以知道ArrayList中存在modCount對象,增刪操作都會使modCount++,通過兩者的對比
迭代器可以快速的知道迭代過程中是否存在list.add()類似的操作,存在的話快速失敗!
Fail-Fast(快速失敗)機制
仔細觀察上述的各個方法,我們在源碼中就會發現一個特別的屬性modCount,API解釋如下:
The number of times this list has been structurally modified. Structural modifications are those that change the size of the list, or otherwise perturb it in such a fashion that iterations in progress
may yield incorrect results.記錄修改此列表的次數:包括改變列表的結構,改變列表的大小,打亂列表的順序等使正在進行 迭代產生 錯誤的結果。
Tips:僅僅設置元素的值並不是結構的修改
我們知道的是ArrayList是線程不安全的,如果在使用迭代器的過程中有其他的線程修改了List就會
拋出ConcurrentModificationException這就是Fail-Fast機制。
那麼快速失敗究竟是個什麼意思呢?
在ArrayList類創建迭代器之後,除非通過迭代器自身remove或add對列表結構進行修改,否則在其他
線程中以任何形式對列表進行修改,迭代器馬上會拋出異常,快速失敗。
迭代器的好處
迭代器的好處
通過上述我們明白了迭代是到底是個什麼,迭代器的使用也十分的簡單。現在簡要的總結下使用迭代器的好處吧。
- 迭代器可以提供統一的迭代方式。**
- 迭代器也可以在對客戶端透明的情況下,提供各種不同的迭代方式。
不過對於第三點尚需注意的是:就像上述事例代碼一樣,我們不能保證迭代過程中出現“快速失敗”的都是因爲同步造成的,因此爲了保證迭代操作的正確性而去依賴此類異常是錯誤的!