2013-10-22 110 views
3

我正在從事某些任務,偶然做錯了根據我但代碼執行並提供正確的結果。我感到有點驚訝,並且懷疑每個循環的所有這些是如何工作的。 示例(示例程序),Java爲每個循環工作

public static void main(String[] args) 
{ 
    String myInput = "hello , hi , how are you "; 
    String[] splitted = myInput.split(","); 
    List<String> mylist = new ArrayList<String>(); 
    for (String output : splitted) 
    { 
     mylist.add(output); 
    } 


    for (String output : mylist) 
    { 
     System.out.println(output); 
     mylist = new ArrayList<String>(); //It worked 
     mylist.add(output); 
    } 

    for (String output : splitted) 
    { 
     mylist.add(output); 
    } 

    for (String output : mylist) 
    { 
     System.out.println(output);    
     mylist.add(output); // After this line it threw exception java.util.ConcurrentModificationException 
    } 

} 

我很想知道,雖然搜索,我發現多了一個帖子說我們可以從列表中,如果我們使用迭代的方法刪除元素,所以,我想,

for (String output : splitted) 
{ 
    mylist.add(output); 
} 
for (Iterator iterator2 = mylist.iterator(); iterator2.hasNext();) 
{ 
    String string = (String) iterator2.next(); 
    System.out.println(string); 
    iterator2.remove(); //It worked but if I used the same thing to remove element from original list it threw exception. 
} 

現在我只想知道上面引用的每個循環的每一個背後發生了什麼。
我想知道技術方​​面,我知道我不能修改每個循環中的集合,但在某些情況下,上述說明它爲什麼?

回答

3

現在,我只是想知道什麼是背後的每一個發生的每個 以上循環

1. for (String output : splitted) 
    { 
     mylist.add(output); 
    } 

援引這增加了從splitted陣列到mylist列表中的每個output字符串。

2. for (String output : mylist) 
{ 
     System.out.println(output); 
     mylist = new ArrayList<String>(); //It worked 
     mylist.add(output); 
} 

for語句由以下生產支配:

for (FormalParameter : Expression) 
      Statement 

其中Expression必須是java.lang.Iterable的實例,或者陣列。因此,這for:each循環是相同的:如果你正在創造新的ArrayList實例並賦予它們

public Iterator<E> iterator() { 
     return new Itr(); 
} 

所以甚至mylist每個:

Iterator<String> iterator = mylist.iterator(); 
while (iterator.hasNext()) { 
    System.out.println(output); 
    mylist = new ArrayList<String>(); //It worked 
    mylist.add(output); 
} 

這裏mylist.iterator()將返回Iterator類型的新實例迭代,從原始mylist獲得的迭代器仍然會參考原始的mylist,並將繼續遍歷原始mylist的元素。迭代器保持對它創建的列表的引用。賦值mylist = new ArrayList<String>()對迭代器工作的數據沒有影響,因爲它更改變量mylist而不是list本身。

3. for (String output : mylist) 
    { 
     System.out.println(output);    
     mylist.add(output); // After this line it threw exception java.util.ConcurrentModificationException 
    } 

下面的語句解釋了這種行爲。它是由Arraylist文檔複製:

此類的iterator和listIterator方法返回的迭代器是快速失敗的:如果列表在任何時間從結構上修改創建迭代器之後,以任何方式,除了通過迭代器自己刪除或添加方法,迭代器將拋出ConcurrentModificationException。因此,面對併發修改,迭代器快速而乾淨地失敗,而不是在將來某個未確定的時間冒着任意的,非確定性的行爲風險。

4. for (Iterator iterator2 = mylist.iterator(); iterator2.hasNext();) 
{ 
    String string = (String) iterator2.next(); 
    System.out.println(string); 
    iterator2.remove(); //It worked but if I used the same thing to remove element from original list it threw exception. 
} 

上面的語句也說明了這個for循環的行爲:list可以通過迭代器自身的remove進行結構修飾或添加方法,同時通過列表進行迭代。

+0

您應該添加第二個循環,因爲迭代器保留對其創建列表的引用。賦值'mylist = new ArrayList ()'對迭代器工作的數據沒有影響,因爲它改變了變量mylist而不是列表本身。 –

+0

@ViktorSeifert同意。編輯我的答案:複製你的陳述。 –

2

對於實施Iterable的類,每個循環都是可能的。這也意味着您可以自己創建Classes,您可以在其中使用每個循環,這可以非常舒適。

該界面強制您實施方法iterator(),該方法返回Iterator。然後for-each循環什麼都不做,只是檢索該迭代器並使用hasNext()next()迭代它。就像你自己會做的一樣。

刪除的問題是,當您使用for-each循環,然後從列表中刪除元素時,構造的Iterator將不知道有關此更改的任何內容,並且將會有ConcurrentModificationException

但是,如果直接調用Iterator.remove(),迭代器將知道該更改並可以處理它。

一個常見的小動作,以避免迭代器和異常在同一時間做這樣的事情:

List<Object> objects = new ArrayList<Object>(); 
for (Object object : new ArrayList<Object>(objects)) { 
    objects.remove(object); 
} 

所以你創建一個目錄的臨時副本,遍歷這一點,但呼籲消除對原始列表。

+0

創建此副本很愚蠢。只需使用迭代器遍歷ArrayList(無論如何,因爲這就是foreach所做的),允許您添加和刪除項目而不會出現任何錯誤,而無需創建副本。如果你需要同時做到這一點,那麼就有一個完全相同的列表。 – TwoThe

+1

@TwoThe我們生活在2013年。創建列表的副本在99%的情況下都不成問題。我認爲比使用迭代器更容易閱讀,在這種情況下,我喜歡使用這個技巧。如果你認爲它很愚蠢,那麼不要使用它。我還將它看作是對問題中所述問題的進一步解釋...... – noone

+0

這是一種完全無用的操作,此外還規避了Java中稱爲ConcurrentModificationException的警告機制。所以這不僅是不必要的,而且還隱藏了潛在的錯誤。爲什麼使用類似的東西,如果有一個簡單的替代方案? – TwoThe

0

這是正確的。你不能修改使用「foreach」循環迭代的集合的值,要做到這一點,你必須使用集合的迭代器。

0

將一些東西添加到完全不同的列表當然不是問題,就像您對mylist = new ArrayList<String>();行做的那樣,即使該變量仍然具有相同的名稱,它將指向完全不同的列表不同的列表。

爲什麼你不能添加一些東西到目前正在「走過」的列表是,該列表的內部實現可能無法確保,你仍然得到相同的元素順序,特別是不您所期望的所有其餘元素。 如果你想象你正在使用一個排序列表,那麼這可以理解得最清楚:你放入一個新元素,但是否看到該元素是未定義的,因爲它取決於你在哪裏和插入什麼。由於Java不知道你是否可以這樣做,它需要安全的道路並拋出異常。

然而,有一些列表能夠在遍歷期間被修改,主要是concurrent package中的併發列表。

2

for-each循環列表將被內部轉換爲循環迭代器

for (String output : mylist) 
     { 
      System.out.println(output); 
      mylist = new ArrayList<String>(); //It worked 
      mylist.add(output); 
     } 

被轉換爲

for (Iterator<String> iterator = mylist.iterator(); iterator.hasNext();) { 
     String output = (String)iterator.next(); 
     System.out.println(output); 
     mylist = new ArrayList<String>(); //It worked 
     mylist.add(output); 
     } 

而且由於該表的快照已經採取低於

for (Iterator<String> iterator = mylist.iterator(); iterator.hasNext();) { 

的循環運行,直到列表即最後一個元素「你怎麼樣您」。

鑑於,下面不是因爲List的FailFast behaviour工作。

for (String output : mylist) 
    { 
     System.out.println(output);    
     mylist.add(output); // After this line it threw exception java.util.ConcurrentModificationException 
    } 

它說,如果你要修改的列表,而迭代,比迭代器自身的remove方法之外的任何東西,列表將拋出ConcurrentModificationException,那下面是工作的原因。

for (Iterator iterator2 = mylist.iterator(); iterator2.hasNext();) 
{ 
    String string = (String) iterator2.next(); 
    System.out.println(string); 
    iterator2.remove(); //It worked but if I used the same thing to remove element from original list it threw exception. 
}