java--迭代(二)for,foreach和迭代器詳解


這篇文章會詳解上篇關於迭代器中出現的問題,當然說是詳解,其實我也只能在自己能力內對foreach,迭代器的機制進行了解。其中以arraylist為例子,包含了jdk的源代碼。

 首先,for是大家都很熟悉的循環語法,它的基礎規則和使用為:

編程中用於循環處理的語句。Java的for語句形式有兩種:一種是和C語言中的for語句形式一樣,另一種形式用於在集合和數組之中進行迭代。有時候把這種形式稱為增強的for(enhanced for)語句,它可以使循環更加緊湊和容易閱讀。它的一般形式為: for(;;) 語句; 初始化總是一個賦值語句,它用來給循環控制變量賦初值;條件表達式是一個關系表達式,它決定什么時候退出循環;增量定義循環控制變量每循環一次后按什么方式變化。這三個部分之間用";"分開。

這一段是話百度的解釋,但事實上這個地方有一點不太准確的地方,foreach確實只能針對iterator進行迭代,但是與我在開頭所提到的迭代器遍歷是由一定區別的,我在后面會通過csdn上某位同志遇到的問題進行詳細解釋。所以在這個時候,我更願意將for,foreach和迭代器遍歷着三種方法分開,而不是將foreach歸為迭代器的一種遍歷方法。

三種方式的區別:

1)形式區別:

對於for循環,我們采用:

forint i=0;i<arr.size();i++){...}

對於foreach:

forint i:arr){...}

(當然這里的int只是舉例子,這里可以是所有實現iterator的接口對象);

在這里多說兩句,foreach只能針對實現了iterable接口的對象,其中具體怎么實現的,我有做了部分了解。我去查看了一個foreach的測試程序的字節碼。當然這不是今天的重點,我會重新開一個篇章講述關於java匯編中字節碼的事情。如果有興趣的同學可以點擊下面這個網站:

 http://www.cnblogs.com/vinozly/p/5465454.html;

 

對於迭代器的形式:

Iterator it = arr.iterator();
while(it.hasNext()){ object o =it.next(); ...}

2)條件差別

for需要知道數組或者集合的大小,而且需要時有序的,不然無法遍歷;

foreach和iterator不需要知道數組或者集合的大小,他們都是得到集合內的每一個元素然后進行處理;

3)多態差別

 for和foreach都需要知道自己的集合類型,甚至要知道自己集合內的元素類型,不能實現多態。這個使用的語法上都可以表示出來。

Iterator是一個接口累心,它不關心集合的累心和集合內的元素類型,因為它是通過hasnext和next來進行下一個元素的判斷和獲取,這一切都是在集合類型定義的時候就完成的事情。迭代器統一了對容器的訪問模式,這也是對接口解耦的最好表現。

4)用法差別

for一般可以用於簡單的順序集合,並且可以預測集合的大小;

foreach可以遍歷任何集合或者數組,但是使用者需要知道遍歷元素的類型。

iterator是最強大的,它可以隨之修改元素內部的元素。可以在遍歷的時刻用remove()!!!!我的意思是其他兩個都不可以!!!

而且iterator不需要知道元素類型和元素大小,通過hasnext()判斷是否遍歷完所有元素。

而且在對范型的集合進行遍歷的時候,iterator是不二的選擇,就是因為不需要知道元素類型便可以遍歷。

 

這里討論完了三個具體的區別,但是我接觸到了一個新的東西叫接口解耦。好吧,我會再寫一個文章講一下接口解耦是什么。(感覺坑越填愈多)

 

接下來講一下iterator實現遍歷的具體機制:

我們首先從一個問題程序入手,這個程序如下:

 1 package iteratorTest;
 2 import java.util.*;
 3 
 4 public class TestIterator {
 5     public static void main(String[] args){
 6         List<String> list=new ArrayList<String>();
 7         
 8         list.add("1");
 9         list.add("2");
10         for(String temp : list){
11             if("2".equals(temp))
12             list.remove(temp);
13         }
14         
15         for(String temp : list){
16             System.out.println(temp);
17         }
18         
19         Iterator<String> it=list.iterator();
20         /*while(it.hasNext()){
21             String temp=it.next();
22             if("2".equals(temp))
23                 it.remove();
24         }
25         
26         for(String temp:list){
27             System.out.println(temp);
28         }*/
29     }
30 }

 

我們匯編的時候,程序會出現下面的錯誤:

當然,我如果更改程序把“1”刪除,這個時候就沒有問題。我當時也是一臉懵逼呀。

從錯誤中可以看到,是checForComodification出現了報錯。。那我們現在就去看這個地方的源代碼:

     final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

這里設計一個java關於多線程的知識,因為iterator是禁止多線程操作的(當然vector的使用會在在后面寫。- -!真的是坑越填越多),java用了一個非常簡單的機制,就是modcount。記錄對集合進行改變的數量,創建一個迭代器與當前集合是要緊耦合的。我不能再我遍歷的時候同時去改變這個集合元素,不然會造成混亂。

 

好,這里我們可以確定是modcount不等於expectedmodecount造成的錯誤,那么及時在foreach內調用next()方法的時候出現了這個問題。我們深入去查看.next()方法。在看之前,我想先把JDK文檔中arraylist的方法和數據結構給出來,這在后面理解的時候是非常重要的:

注意我所標記的fastremove方法上面的remove()方法和私有類Itr里面的remove()方法。很明顯Itr是一個iterator接口類。注意了!!!但是在foreach的循環語句里面,我們使用的是上面的remove方法,因為我們並沒有構造list的迭代器。但是報道的錯誤信息來源於這個私有類里面的next是foreach擅自為我們進行構造的迭代器,而我們沒有使用這個迭代器里面的remove()!!!!!在研究源碼的時候我真的是載在這里好久好久。

我們使用的list.remove()是使用的arraylist自身的remove()方法,而不是構造器的方法。在使用的時候我還是清醒的,在調查源代碼的過程中整個人都懵逼了。我給出兩個remove的具體代碼,大家就會知道,arraylist自身方法的remove是不會去調整exceptedmodcount的值,但是卻增加了modcount,所以在foreach語句中使用next的checkmodcount的時候發生了錯誤。

arraylist.remove():

    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

    /*
     * Private remove method that skips bounds checking and does not
     * return the value removed.
     */
    private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }

 

Itr私有迭代器的remove():

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

 

很明顯迭代器中的remove有意識的改變expectedmodcount來使其不報錯。而foreach使用的arraylist.remove,改變了modcount,所以才會出現報錯現象。

 

然而刪除“1”不會報錯,是因為刪除過后foreach會調用迭代器中的hasnext()進行判斷,代碼如下:

        public boolean hasNext() {
            return cursor != size;
        }

 

這個時候沒有下一個元素,所以不會在arraylist.remove()后調用Itr.next()方法,所以不會進行判斷。

這里給出Itr.next()的代碼:

        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];
        }

可以看到,在Itr.next()的一開始便檢查modcount於expectedmodcount是否相同。

 

通過上面的了解,我們使用iterator為什么能夠在遍歷的時候進行刪除操作也可以理解了:

在Itr.remove()方法內有意的將expectedmodcount賦值為modcount,所以不會產生checkformodcount的錯誤。

 

ps:真的,我在這里研究這么久是因為我看了一個評論,它就是將foreach的機制和arraylist.remove, Itr.remove弄混了,一直在分析迭代器內部是如何影響modcount和expectedcount的。還得到了很多很多贊。我在理解源碼的過程中表示,這不是吧expectedmodcount也改變了的么。

 

所以有時候,大家最好能通過源碼了解的,都自己去看源碼吧,大家對問題有不同的看法,也會有錯誤,但是源碼是不變的。

這篇文章寫到這里,很多問題也沒有深入,關於foreach的實現和字節碼,有時間就會做的。

我還會另開一個數據庫的篇章,以懷戀我drop掉的數據庫課。不是我不想上,是印度教授真的弄的我頭疼。

最后,我想說的就是:

開源大法好!!!!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM