奇葩迭代器筆試題,通常人都作不對

有位小朋友最近正在爲年後換工做作準備,可是遇到一個問題,以爲很難以想象的一道筆試題。而後我把這道題發到技術羣裏,發現不少人竟然不知道,不少都是連蒙帶猜的說。感受頗有必要寫一篇文章來講道說道。java

奇怪的筆試題

閱讀下面這段代碼,請寫出這段代碼的輸出內容:面試

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");
    }}

他寫出來的答案是:算法

134

奇怪的是,你把這道題目發給你身邊人,讓他們回答這道面試題輸出結果是什麼,說這個結果的人很是多。不行你試試設計模式


e72fd5551521dd64f8e1492ffd5473dd.png


~數組

答案明顯不對,由於在第一個while裏的 iterator.hasNext()==false後纔會到第二個while裏來,同一個Iterator對象,前面調一次iterator.hasNext()==false,再判斷一次結果不仍是同樣嗎?,數據結構

因此第二個while判斷爲false,也就不會再去遍歷iterator了,由此可知本體答案是:4。app

下面咱們來分析一下爲何是具體底層是怎麼實現的。ide

這裏的Iterator是什麼?

  • 迭代器是一種模式、詳細可見其設計模式,可使得序列類型的數據結構的遍歷行爲與被遍歷的對象分離,即咱們無需關心該序列的底層結構是什麼樣子的。只要拿到這個對象,使用迭代器就能夠遍歷這個對象的內部
  • Iterable 實現這個接口的集合對象支持迭代,是能夠迭代的。實現了這個能夠配合foreach使用~
  • Iterator 迭代器,提供迭代機制的對象,具體如何迭代是這個Iterator接口規範的。

Iterator說明

public interface Iterator<E> { 
    //每次next以前,先調用此方法探測是否迭代到終點    boolean hasNext();
    //返回當前迭代元素 ,同時,迭代遊標後移    E next(); 
    /*刪除最近一次已近迭代出出去的那個元素。     只有當next執行完後,才能調用remove函數。     好比你要刪除第一個元素,不能直接調用 remove()   而要先next一下( );     在沒有先調用next 就調用remove方法是會拋出異常的。     這個和MySQL中的ResultSet很相似    */
    default void remove() {
        throw new UnsupportedOperationException("remove");
    } 
    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }}

這裏的實現類是ArrayList的內部類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中的屬性,當添加或刪除的時候moCount值會增長或者減小        //這裏主要是給fail-fast使用,避免一遍在遍歷,一遍正在修改致使數據出錯        //此列表在結構上被修改的次數。結構修改是指改變結構尺寸的修改列表,        //或者以這樣的方式對其進行擾動,進步可能會產生錯誤的結果。        int expectedModCount = modCount;
        public boolean hasNext() {
            //cursor初始值爲0,沒掉一次next方法就+1            //size是ArrayList的大小            return cursor != size;
        }
        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            //把ArrayList中的數組賦給elementData            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            //每調用一次next方法,遊標就加1            //cursor=lastRet+1            cursor = i + 1;
            //返回ArrayList中的元素            return (E) elementData[lastRet = i];
        }
        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();
            try {
                //調用ArrayList中remove方法,溢出該元素                ArrayList.this.remove(lastRet);
                //cursor=lastRet+1,                //因此此時至關於cursor=cursor-1                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }}

再回到上面題目中:ui

第一個iterator.hasNext()

第1次循環

  • hasNext方法中:cursor==0, size==3,因此cursor != size返回true。
  • next方法中:cursor=0+1。返回"1"。

第2次循環

  • 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次循環

  • hasNext方法中:cursor==1, size==2,那麼cursor != size返回true。
  • next方法中:cursor=1+1==2;返回"3"。

第4次循環

  • hasNext方法中:cursor==2, size==2,那麼cursor != size返回false。

第二個iterator.hasNext()

hasNext方法中:cursor==2, size==2,因此cursor != size返回false。

因此,最後只輸出"4",即答案爲4.

Iterator與泛型搭配

  • Iterator對集合類中的任何一個實現類,均可以返回這樣一個Iterator對象。能夠適用於任何一個類。
  • 由於集合類(List和Set等)能夠裝入的對象的類型是不肯定的,從集合中取出時都是Object類型,用時都須要進行強制轉化,這樣會很麻煩,用上泛型,就是提早告訴集合肯定要裝入集合的類型,這樣就能夠直接使用而不用顯示類型轉換.很是方便.

foreach和Iterator的關係

  • for each以用來處理集合中的每一個元素而不用考慮集合定下標。就是爲了讓用Iterator簡單。可是刪除的時候,區別就是在remove,循環中調用集合remove會致使原集合變化致使錯誤,而應該用迭代器的remove方法。

使用for循環仍是迭代器Iterator對比

  • 採用ArrayList對隨機訪問比較快,而for循環中的get()方法,採用的便是隨機訪問的方法,所以在ArrayList裏,for循環較快
  • 採用LinkedList則是順序訪問比較快,iterator中的next()方法,採用的便是順序訪問的方法,所以在LinkedList裏,使用iterator較快
  • 從數據結構角度分析,for循環適合訪問順序結構,能夠根據下標快速獲取指定元素.而Iterator 適合訪問鏈式結構,由於迭代器是經過next()和Pre()來定位的.能夠訪問沒有順序的集合.
  • 而使用 Iterator 的好處在於可使用相同方式去遍歷集合中元素,而不用考慮集合類的內部實現(只要它實現了 java.lang.Iterable 接口),若是使用 Iterator 來遍歷集合中元素,一旦再也不使用 List 轉而使用 Set 來組織數據,那遍歷元素的代碼不用作任何修改,若是使用 for 來遍歷,那全部遍歷此集合的算法都得作相應調整,由於List有序,Set無序,結構不一樣,他們的訪問算法也不同.(仍是說明了一點遍歷和集合自己分離了)。

總結

  • 迭代出來的元素都是原來集合元素的拷貝。
  • Java集合中保存的元素實質是對象的引用,而非對象自己。
  • 迭代出的對象也是引用的拷貝,結果仍是引用。那麼若是集合中保存的元素是可變類型的,那麼能夠經過迭代出的元素修改原集合中的對象。
相關文章
相關標籤/搜索