有位小朋友最近正在為年后換工作做準(zhǔn)備,,但是遇到一個(gè)問(wèn)題,,覺(jué)得很不可思議的一道筆試題,。然后我把這道題發(fā)到技術(shù)群里,發(fā)現(xiàn)很多人居然不知道,,很多都是連蒙帶猜的說(shuō),。感覺(jué)很有必要寫(xiě)一篇文章來(lái)說(shuō)道說(shuō)道。 漲薪必備的面試小抄 奇怪的筆試題閱讀下面這段代碼,,請(qǐng)寫(xiě)出這段代碼的輸出內(nèi)容: import java.util.ArrayList; import java.util.Iterator; import java.util.*;
public class Test { public static void main(String[] args) {
List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); list.add("3"); Iterator iterator = list.iterator(); while (iterator.hasNext()) { String str = (String) iterator.next(); if (str.equals("2")) { iterator.remove(); } } while (iterator.hasNext()) { System.out.println(iterator.next()); } System.out.println("4"); } }
他寫(xiě)出來(lái)的答案是: 1 3 4
奇怪的是,,你把這道題目發(fā)給你身邊人,,讓他們回答這道面試題輸出結(jié)果是什么,說(shuō)這個(gè)結(jié)果的人非常多,。不行你試試~ 答案明顯不對(duì),,因?yàn)樵诘谝粋€(gè)while里的 iterator.hasNext()==false后才會(huì)到第二個(gè)while里來(lái),同一個(gè)Iterator對(duì)象,,前面調(diào)一次iterator.hasNext()==false,,再判斷一次結(jié)果不還是一樣嗎?,, 所以第二個(gè)while判斷為false,,也就不會(huì)再去遍歷iterator了,由此可知本體答案是:4,。 下面我們來(lái)分析一下為什么是具體底層是怎么實(shí)現(xiàn)的,。 這里的Iterator是什么?- 迭代器是一種模式,、詳細(xì)可見(jiàn)其設(shè)計(jì)模式,,可以使得序列類(lèi)型的數(shù)據(jù)結(jié)構(gòu)的遍歷行為與被遍歷的對(duì)象分離,即我們無(wú)需關(guān)心該序列的底層結(jié)構(gòu)是什么樣子的,。只要拿到這個(gè)對(duì)象,使用迭代器就可以遍歷這個(gè)對(duì)象的內(nèi)部
- Iterable 實(shí)現(xiàn)這個(gè)接口的集合對(duì)象支持迭代,,是可以迭代的。實(shí)現(xiàn)了這個(gè)可以配合foreach使用~
- Iterator 迭代器,,提供迭代機(jī)制的對(duì)象,,具體如何迭代是這個(gè)Iterator接口規(guī)范的。
Iterator說(shuō)明public interface Iterator<E> { //每次next之前,,先調(diào)用此方法探測(cè)是否迭代到終點(diǎn) boolean hasNext(); //返回當(dāng)前迭代元素 ,,同時(shí),迭代游標(biāo)后移 E next(); /*刪除最近一次已近迭代出出去的那個(gè)元素,。 只有當(dāng)next執(zhí)行完后,,才能調(diào)用remove函數(shù)。 比如你要?jiǎng)h除第一個(gè)元素,,不能直接調(diào)用 remove() 而要先next一下( ); 在沒(méi)有先調(diào)用next 就調(diào)用remove方法是會(huì)拋出異常的,。 這個(gè)和MySQL中的ResultSet很類(lèi)似 */ default void remove() { throw new UnsupportedOperationException("remove"); } default void forEachRemaining(Consumer<? super E> action) { Objects.requireNonNull(action); while (hasNext()) action.accept(next()); } }
這里的實(shí)現(xiàn)類(lèi)是ArrayList的內(nèi)部類(lèi)Itr。 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 //modCountshi ArrayList中的屬性,,當(dāng)添加或刪除的時(shí)候moCount值會(huì)增加或者減少 //這里主要是給fail-fast使用,,避免一遍在遍歷,一遍正在修改導(dǎo)致數(shù)據(jù)出錯(cuò) //此列表在結(jié)構(gòu)上被修改的次數(shù),。結(jié)構(gòu)修改是指改變結(jié)構(gòu)尺寸的修改列表,, //或者以這樣的方式對(duì)其進(jìn)行擾動(dòng),進(jìn)步可能會(huì)產(chǎn)生錯(cuò)誤的結(jié)果。 int expectedModCount = modCount; public boolean hasNext() { //cursor初始值為0,沒(méi)掉一次next方法就+1 //size是ArrayList的大小 return cursor != size; }
@SuppressWarnings("unchecked") public E next() { checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); //把ArrayList中的數(shù)組賦給elementData Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); //每調(diào)用一次next方法,游標(biāo)就加1 //cursor=lastRet+1 cursor = i + 1; //返回ArrayList中的元素 return (E) elementData[lastRet = i]; }
public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification();
try { //調(diào)用ArrayList中remove方法,溢出該元素 ArrayList.this.remove(lastRet); //cursor=lastRet+1,, //所以此時(shí)相當(dāng)于cursor=cursor-1 cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
再回到上面題目中: 第一個(gè)iterator.hasNext()第1次循環(huán)
hasNext方法中:cursor==0, size==3,,所以cursor != size返回true。 next方法中:cursor=0+1,。返回"1",。
第2次循環(huán)
hasNext方法中:cursor==1, size==3,,所以cursor != size返回true,。 next方法中:cursor=1+1。返回"2",。 remove方法中:cursor==cursor-1==2-1=1,,把ArrayList中的"2"給刪除了,所以size==2,。
第3次循環(huán)
hasNext方法中:cursor==1,, size==2,那么cursor != size返回true,。 next方法中:cursor=1+1==2,;返回"3"。
第4次循環(huán)
- hasNext方法中:cursor==2,, size==2,,那么cursor != size返回false。
第二個(gè)iterator.hasNext()hasNext方法中:cursor==2,, size==2,,所以cursor != size返回false。 所以,,最后只輸出"4",,即答案為4. Iterator與泛型搭配- Iterator對(duì)集合類(lèi)中的任何一個(gè)實(shí)現(xiàn)類(lèi),都可以返回這樣一個(gè)Iterator對(duì)象,??梢赃m用于任何一個(gè)類(lèi)。
- 因?yàn)榧项?lèi)(List和Set等)可以裝入的對(duì)象的類(lèi)型是不確定的,從集合中取出時(shí)都是Object類(lèi)型,用時(shí)都需要進(jìn)行強(qiáng)制轉(zhuǎn)化,這樣會(huì)很麻煩,用上泛型,就是提前告訴集合確定要裝入集合的類(lèi)型,這樣就可以直接使用而不用顯示類(lèi)型轉(zhuǎn)換.非常方便.
foreach和Iterator的關(guān)系- for each以用來(lái)處理集合中的每個(gè)元素而不用考慮集合定下標(biāo),。就是為了讓用Iterator簡(jiǎn)單,。但是刪除的時(shí)候,區(qū)別就是在remove,,循環(huán)中調(diào)用集合remove會(huì)導(dǎo)致原集合變化導(dǎo)致錯(cuò)誤,,而應(yīng)該用迭代器的remove方法,。
使用for循環(huán)還是迭代器Iterator對(duì)比- 采用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較快
- 從數(shù)據(jù)結(jié)構(gòu)角度分析,for循環(huán)適合訪(fǎng)問(wèn)順序結(jié)構(gòu),可以根據(jù)下標(biāo)快速獲取指定元素.而Iterator 適合訪(fǎng)問(wèn)鏈?zhǔn)浇Y(jié)構(gòu),因?yàn)榈魇峭ㄟ^(guò)next()和Pre()來(lái)定位的.可以訪(fǎng)問(wèn)沒(méi)有順序的集合.
- 而使用 Iterator 的好處在于可以使用相同方式去遍歷集合中元素,而不用考慮集合類(lèi)的內(nèi)部實(shí)現(xiàn)(只要它實(shí)現(xiàn)了 java.lang.Iterable 接口),,如果使用 Iterator 來(lái)遍歷集合中元素,,一旦不再使用 List 轉(zhuǎn)而使用 Set 來(lái)組織數(shù)據(jù),那遍歷元素的代碼不用做任何修改,,如果使用 for 來(lái)遍歷,,那所有遍歷此集合的算法都得做相應(yīng)調(diào)整,因?yàn)長(zhǎng)ist有序,Set無(wú)序,結(jié)構(gòu)不同,他們的訪(fǎng)問(wèn)算法也不一樣.(還是說(shuō)明了一點(diǎn)遍歷和集合本身分離了)。
總結(jié)- 迭代出來(lái)的元素都是原來(lái)集合元素的拷貝,。
- Java集合中保存的元素實(shí)質(zhì)是對(duì)象的引用,,而非對(duì)象本身。
- 迭代出的對(duì)象也是引用的拷貝,,結(jié)果還是引用,。那么如果集合中保存的元素是可變類(lèi)型的,那么可以通過(guò)迭代出的元素修改原集合中的對(duì)象,。
參考:ii081.cn/2Jc3e 最近有粉絲想要一些電子書(shū),,這次直接分享我收藏的200本技術(shù)類(lèi)電子書(shū),打包發(fā)給大家,,如有需要,,關(guān)注公眾號(hào)領(lǐng)取喲~ 掃以下二維碼并回復(fù)“電子書(shū)”即可獲取點(diǎn)贊越多,bug越少
|