java基礎(三) 增強型for循環與Iterator


引言
html

  從JDK1.5起,增長了增強型的for循環語法,也被稱爲 「for-Each 循環」。增強型循環在操做數組與集合方面增長了很大的方便性。那麼,增強型for循環是怎麼解析的呢?同時,這是否是意味着基本for循環就會被取代呢?java

語法:程序員

for(var item:items){//var 表明各鍾類型
    //相關操做
}
複製代碼

1、數組中的for-each循環

咱們先來看一下數組中的 for-Each 循環的使用;編程

String str[]= new String[]{"1","2","3"};
    //普通for循環
    for(int i=0;i<str.length;i++){
        String item = str[i];
        item += "str";
        System.out.println(item);
    }
    //增強型for循環
    for(String item:str){
        item += "str";
        System.out.println(item);
    }
複製代碼

  經過比較上面例子中的兩種類型的for循環,能夠看出,for-Each 循環編寫起來更加簡單,更加方便程序員。所以,在程序中,應該多使用增強型循環。數組

回答一下上面提出的兩個問題:bash

一、編譯器是怎麼處理數組中的for-Each循環的?微信

事實上,在數組中的 for-Each 最終會被編譯器處理成一個普通的for循環,也就是說 for-Each循環是徹底與普通for循環等價的,沒有任何特殊的命令。
能夠經過反編譯來驗證,很簡單,此處再也不多說。多線程

二、在數組中,for-Each 循環可否徹底替代普通for循環併發

答案是否認的。雖然for-Each 寫起來方便,但也有如下幾個侷限性:ide

  • 只能對元素進行順序的訪問;

  • 只能訪問數組或集合中的全部元素;

  • 循環中沒有當前的索引,沒法對指定的元素操做。如更換當前索引位置的元素。

2、集合中的for-each循環

  數組的增強型的for-Each循環很簡單,咱們再來看一下集合中的for-Each 循環又是怎麼樣的。咱們都知道集合中的遍歷都是經過迭代(iterator)完成的。也許有人說,也能夠按照下面的方式來遍歷集合,不必定非要使用迭代:

List<String> list = new LinkedList<String>();
    list.add("a");
    list.add("b");
    list.add("c");
    for(int i=0;i<list.size();i++){
        String item = list.get(i);
        System.out.println(item);
    }
複製代碼

  然而,這種方式對於基於鏈表實現的List來講,是比較耗性能的,由於get(int i)方法包含了一個循環,並且這個循環就是迭代遍歷一次List,直到遇到第i個元素,才中止循環,返回第i個元素。對於數量小,遍歷不頻繁的List來講,開銷能夠忽略。不然,開銷將不容忽視。

因此,正確集合遍歷是使用迭代器Iterator來遍歷的:

List<String> list = new LinkedList<String>();
    list.add("a");
    list.add("b");
    list.add("c");
    //獲取集合的迭代器
    Iterator<String> itor = list.iterator();
    //集合的普通for循環
    for(;itor.hasNext();){//至關於 while(itor.hasNext())
        String item = itor.next();
        System.out.println(item);
    }
複製代碼

再看看對應的for-Each循環的例子:

List<String> list = new LinkedList<String>();
    list.add("a");
    list.add("b");
    list.add("c");
    for(String item:list){//for-Each
        System.out.println(item);
    }
複製代碼

能夠看出,for-Each循環比普通for循環要簡潔不少。咱們依舊回答上面的兩個問題:

  1. 編譯器是如何處理 集合中的for-Each循環的?

public static void main(String args[])
    {
        List list = new LinkedList();
        list.add("aa");
        list.add("bb");
        for(String item:list)
        {
            if("bb".equals(item))
                list.add("cc");
        }
    }
複製代碼

咱們看一下上面例子的 反編譯代碼

public static void main(String args[])
    {
        List list = new LinkedList();
        list.add("aa");
        list.add("bb");
        for(Iterator iterator = list.iterator(); iterator.hasNext();)
        {
            String item = (String)iterator.next();
            if("bb".equals(item))
                list.add("cc");
        }
    }
複製代碼

  與數組相似,編譯器最終也就是將集合中的for-Each循環處理成集合的普通for循環。 而集合的Collection接口經過擴展Iterable接口來提供iterator()方。那麼咱們換一個角度,是否是隻要實現 Iterable接口,提供iterator()方法,也可使用 for-Each循環呢?來看個例子:

class  MyList<T> implements Iterable<T>{
    private ArrayList<T> list = new ArrayList<>();
    public void addId(T id){
        list.add(id);
    }
    public boolean removeId(T id){
        return list.remove(id);
    }
    @Override
    public Iterator<T> iterator() {//擴展自Iterable接口
           //爲了簡單起見,就直接使用已有的迭代器
        return list.iterator();
    }
    public static void main(String[] args) {
        MyList<String> myList = new MyList<>();
        myList.addId("666999");
        myList.addId("973219");
             //for-Each
        for(String item:myList){
            System.out.println(item);
        }
    }
}
複製代碼

  上面的例子編譯經過,而且運行無誤。因此,只要實現了Iterable接口的類,均可以使用for-Each循環來遍歷。

一、集合迭代的陷阱

  集合循環遍歷時所使用的迭代器Iterator有一個要求:在迭代的過程當中,除了使用迭代器(如:Iterator.remove()方法)對集合增刪元素外,是不容許直接對集合進行增刪操做。不然將會拋出 ConcurrentModificationException異常。因此,因爲集合的for-Each循環本質上使用的仍是Iterator來迭代,所以也要注意這個陷阱。for-Each循環很隱蔽地使用了Iterator,致使程序員很容易忽略掉這個細節,因此必定要注意。看下面的例子,for-Each循環中修改了集合。

public static void main(String[] args) {
        List<String> list = new LinkedList<>();
        list.add("aa");
        list.add("bb");
        for (String item : list) {//for-Each
            if ("bb".equals(item)) {
                list.add("cc"); //直接操做list
            }
        }
    }
複製代碼

運行拋出異常:


  上面僅僅是 單線程 下的狀況,若是你有併發編程的基礎的話,就會知道:在 多線程 的環境中,線程是交替運行的(時間片輪轉調度)。這就意味着,若是有兩個線程A、B,線程A對集合使用Iterator迭代遍歷,線程B則對集合進行增刪操做。線程A、B一旦交替運行,就會出如今迭代的同時對集合增刪的效果,也會拋出異常。解決辦法就是加鎖變成原子操做,多線程在這裏不是本文重點,很少說了。

二、集合中的for-each循環能代替集合中的普通for循環嗎?

  一樣也是不能的。集合中的for-Each循環的侷限性與數組的for-Each循環是同樣的。集合的for-Each循環是不能對集合進行增刪操做、也不能獲取索引。而集合的普通for循環可使用的迭代器提供了對集合的增刪方法(如:Iterator.removeListIterator.add()),獲取索引的方法(如:ListIterator.nextIndex()ListIterator.previousIndex());

3、Iterator源碼分析

  咱們來分析一下Iterator源碼,主要看看爲何在集合迭代時,修改集合可能會拋出ConcurrentModificationException異常。以ArrayList中實現的Iterator爲例。

先來看一下ArrayList.iterator()方法,以下:

public Iterator<E> iterator() {
        return new Itr();
    }
複製代碼

iterator()方法直接建立了一個類Itr的對象。那就接着看 Itr類的定義吧!發現Itr實際上是ArrayList的內部類,實現了 Iterator 接口。

/**
     * An optimized version of AbstractList.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
        int expectedModCount = modCount;
        public boolean hasNext() {
            return cursor != size;
        }
        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            //ArrayList的底層數組
            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
                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();
        }
    }
複製代碼

  ArrayList.this.elementDataArrayList的底層數組,上面這些方法都很簡單,都是對ArrayList.this.elementData這個底層數組進行操做。
  重點看一下checkForComodification()方法,這個方法就是用來拋出 ConcurrentModificationException異常,這個方法也很簡單,就是判斷modCountexpectedModCount是否相等。modCount存儲的AarryList中的元素個數。而expectedModCount則是對象建立時將modCount的值賦給它,也就是說expectedModCount存儲的是迭代器建立時元素的個數。那麼checkForComodification()方法其實在比較迭代期間,ArrayList元素的個數 是否發生了改變,若是改變了,就拋出異常。注意一下,expectedModCount除了在聲明時賦值外,也在remove()方法中更新了一次。

總結

  • 不管是在數組中仍是在集合中,for-Each增強型for循環都是它們各自的普通for循環的一種「簡寫方式」,即二者意思上是等價的,但前者方便簡單,建議多使用。

  • for-Each循環不能徹底代替普通for循環,由於for-Each有必定的侷限性。

  • for-Each循環只能用於 數組、Iterable類型(包括集合)。

  • 集合中的for-Each循環本質上使用了Ierator迭代器,因此要注意Itrator迭代陷阱(單線程和多線程都有問題)。

出處:http://www.cnblogs.com/jinggod/p/8424868.html

文章有不當之處,歡迎指正,你也能夠關注個人微信公衆號:`好好學java`,獲取優質學習資源。

相關文章
相關標籤/搜索