首頁  >  文章  >  类库下载  >  JAVA中的for-each迴圈與迭代

JAVA中的for-each迴圈與迭代

高洛峰
高洛峰原創
2016-10-19 09:59:151769瀏覽

一、迭代器Iterator

介面:Iterator

public interface Iterator<E>{

 boolean hasNext();

 E next();

 void remove();
 }

查看Iterator介面API可以知道,這是對collection進行迭代的迭代器。迭代器允許呼叫者利用定義良好的語意在迭代期間從迭代器所指向的 collection 移除元素。 

尤其值得注意的是此迭代器remove()方法的使用:從迭代器指向的 collection 中移除迭代器返回的最後一個元素(可選操作)。每次呼叫 next 只能呼叫一次此方法。如果進行迭代時以呼叫此方法(remove方法)以外的其他方式修改了該迭代器所指向的 collection,則迭代器的行為是不確定的。 介面設計人員在設計Iterator介面的時候已經指出,在進行迭代時如果呼叫了除了迭代器的remove()方法修改了該迭代器所指向的collection,則會造成不確定的後果。具體出現什麼後果依迭代器的具體實作而定。針對這種不確定的後果可能出現的情況,在學習ArrayList時遇到了其中一種:迭代器拋出ConcurrentModificationException異常。具體異常情況如下程式碼所示:

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class ItaratorTest {

    public static void main(String[] args) {
        Collection<String> list = new ArrayList<String>();
        list.add("Android");
        list.add("IOS");
        list.add("Windows Mobile");

        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            String lang = iterator.next();
            list.remove(lang);//will throw ConcurrentModificationException
        }
    }

}

此段程式碼在執行時會拋出ConcurrentModificationException異常,因為我們在迭代器運行期間沒有用iterator的remove()方法來刪除元素,而是使用ArrayList的remove()方法改變了迭代器所指向的collection。這就違反了迭代器的設計原則,所以發生了異常。
所報異常情況如下所示:

Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
    at java.util.ArrayList$Itr.next(ArrayList.java:831)
    at Text.ItaratorTest.main(ItaratorTest.java:17)

二、for-each循環與迭代器Iterator

從Java5起,在Java中有了for-each循環,可以用來循環遍歷collection和array。 Foreach迴圈允許你在無需保持傳統for迴圈中的索引,或在使用iterator /ListIterator(ArrayList中的一種迭代器實作)時無需呼叫while迴圈中的hasNext()方法就能遍歷collection。 for-each迴圈簡化了任何Collection或array的遍歷過程。但是使用foreach循環也有兩點需要注意。

使用foreach循環的對象,必須實作了Iterable介面

請看如下範例:

import java.util.ArrayList;

public class ForeachTest1 {

    public static void main(String args[]) {
        CustomCollection<String> myCollection = new CustomCollection<String>();
        myCollection.add("Java");
        myCollection.add("Scala");
        myCollection.add("Groovy");

        // What does this code will do, print language, throw exception or
        // compile time error
        for (String language : myCollection) {
            System.out.println(language);
        }
    }

    private class CustomCollection<T> {
        private ArrayList<T> bucket;

        public CustomCollection() {
            bucket = new ArrayList();
        }

        public int size() {
            return bucket.size();
        }

        public boolean isEmpty() {
            return bucket.isEmpty();
        }

        public boolean contains(T o) {
            return bucket.contains(o);
        }

        public boolean add(T e) {
            return bucket.add(e);
        }

        public boolean remove(T o) {
            return bucket.remove(o);
        }

    }
}

上述程式碼將無法透過編譯,這是因為程式碼中的CustomCollection類別沒有實作Iterable介面,編譯期的報錯:

Exception in thread "main" java.lang.Error: Unresolved compilation problem: 
    Can only iterate over an array or an instance of java.lang.Iterable

    at Text.ForeachTest1.main(ForeachTest1.java:15)

事實上,無需等到編譯時才發現報錯,eclipse會在這段程式碼寫完之後就會在foreach循環處顯示錯誤:Can only iterate over an array or an instance of java.lang.Iterable

從上述範例可以再次確認的是,foreach循環只適用於實作了Iterable介面的物件。由於所有內建Collection類別都實作了java.util.Collection接口,已經繼承了Iterable,所以為了解決上述問題,可以選擇簡單地讓CustomCollection實作Collection介面或繼承AbstractCollection。解決方式如下:

import java.util.AbstractCollection;
import java.util.ArrayList;
import java.util.Iterator;

public class ForeachTest {
    public static void main(String args[]) {
        CustomCollection<String> myCollection = new CustomCollection<String>();
        myCollection.add("Java");
        myCollection.add("Scala");
        myCollection.add("Groovy");
        for (String language : myCollection) {
            System.out.println(language);
        }
    }

    private static class CustomCollection<T> extends AbstractCollection<T> {
        private ArrayList<T> bucket;

        public CustomCollection() {
            bucket = new ArrayList();
        }

        public int size() {
            return bucket.size();
        }

        public boolean isEmpty() {
            return bucket.isEmpty();
        }

        public boolean contains(Object o) {
            return bucket.contains(o);
        }

        public boolean add(T e) {
            return bucket.add(e);
        }

        public boolean remove(Object o) {
            return bucket.remove(o);
        }

        @Override
        public Iterator<T> iterator() {
            // TODO Auto-generated method stub
            return bucket.iterator();
        }
    }
}

  2.foreach循環的內部實現也是依靠Iterator進行實現的

為了驗證foreach循環是使用Iterator作為內部實現這一事實,我們依然採用本文最開始的實例進行驗證:

reee

程式執行時期所報異常:

public class ItaratorTest {

    public static void main(String[] args) {
        Collection<String> list = new ArrayList<String>();
        list.add("Android");
        list.add("IOS");
        list.add("Windows Mobile");

        // example1
        // Iterator<String> iterator = list.iterator();
        // while (iterator.hasNext()) {
        // String lang = iterator.next();
        // list.remove(lang);
        // }

        // example 2
        for (String language : list) {
            list.remove(language);
        }
    }

}

此異常正說明了for-each循環內部使用了Iterator來遍歷Collection,它也調用了Iterator.next(),這會檢查(元素的)變化並拋出ConcurrentModificationException。

總結:

在遍歷collection時,如果要在遍歷期間修改collection,則必須透過Iterator/listIterator來實現,否則可能會發生「不確定的後果」。

foreach循環透過iterator實現,使用foreach循環的物件必須實現Iterable介面

 


陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn