2009-06-14 63 views
66

是否可以在迭代元素的同時向集合添加元素?Java:在迭代過程中向集合添加元素

更具體地說,我想迭代一個集合,並且如果一個元素滿足某個條件,我想向集合中添加一些其他元素,並確保這些添加的元素也被迭代。 (我知道這可能導致unterminating循環,但我敢肯定它不會在我的情況。)

Sun的Java Tutorial表明,這是不可能的:「請注意,Iterator.remove只有在迭代期間修改集合的安全方法;如果在迭代過程中以任何其他方式修改了基礎集合,則行爲未指定。

所以,如果我不能做我想用迭代器做什麼,你建議我做什麼?

回答

52

如何建立一個隊列與你想迭代的元素;當您想要添加元素時,將它們排入隊列末尾,並保持刪除元素,直到隊列爲空。這是廣度優先搜索通常如何工作的。

+1

如果有人想詳細說明這個想法,請隨意...... – Avi 2009-06-14 15:51:09

+2

如果它適合OP編碼的模型,這是一種很好的方法。這樣你就不用迭代器 - 只是一個while循環。當隊列中有元素時,處理第一個元素。但是,您也可以使用List來做到這一點。 – Eddie 2009-06-14 16:13:52

+0

我不明白。 – inetphantom 2015-04-23 09:35:35

-1

一般,這是不安全的,雖然對於一些收藏它可能是。明顯的選擇是使用某種for循環。但是你沒有說你正在使用什麼樣的收藏,所以這可能會也可能不會。

43

這裏有兩個問題:

,則返回的第一個問題添加到CollectionIterator後。如前所述,有當底層Collection修改沒有定義的行爲,如文檔中提到了Iterator.remove

...迭代器的行爲是不確定 如果標的 集合,而 修改除了通過調用此方法以外,任何方式的迭代都以 進行。

第二個問題是,即使Iterator可以得到,然後返回到相同的元素Iterator在,沒有對iteratation的順序保證,因爲Collection.iterator方法文檔中指出:

...有關於 爲了不保證其中的元素是 返回(除非該集合是一些類,提供一個 擔保的 實例)。

例如,假設我們有名單[1, 2, 3, 4]

假設5加入時Iterator3,不知何故,我們得到了一個Iterator可以從4恢復迭代。但是,沒有保證5會在4之後。迭代順序可能是[5, 1, 2, 3, 4] - 那麼迭代器仍然會錯過元素5

由於不能保證行爲,人們不能認爲事情會以某種方式發生。

一種替代可以有一個單獨的Collection到新創建的元素可以被添加到,然後再遍歷這些元素:

Collection<String> list = Arrays.asList(new String[]{"Hello", "World!"}); 
Collection<String> additionalList = new ArrayList<String>(); 

for (String s : list) { 
    // Found a need to add a new element to iterate over, 
    // so add it to another list that will be iterated later: 
    additionalList.add(s); 
} 

for (String s : additionalList) { 
    // Iterate over the elements that needs to be iterated over: 
    System.out.println(s); 
} 

編輯

在闡述Avi's answer,它有可能將我們想要迭代的元素排隊到一個隊列中,並在隊列中有元素時刪除這些元素。這將允許除了原始元素之外的新元素的「迭代」。

讓我們來看看它是如何工作的。

從概念上講,如果我們在隊列中的以下內容:

[1, 2, 3, 4]

而且,當我們刪除1,我們決定增加42,隊列將是如下所示:

[2, 3, 4, 42]

由於隊列是一個FIFO(先進先出)數據結構,因此該排序是典型的。 (至於Queue接口的文檔中提到的,這不是一個Queue的必需品。取PriorityQueue哪些訂單可以通過自然順序的元素的情況下,所以這不是FIFO。)

下面是使用一個例子一個LinkedList(這是一個Queue),以便通過所有元素以及在出列期間添加的其他元素。類似於上面的示例中,當元件2除去元件42加入:

Queue<Integer> queue = new LinkedList<Integer>(); 
queue.add(1); 
queue.add(2); 
queue.add(3); 
queue.add(4); 

while (!queue.isEmpty()) { 
    Integer i = queue.remove(); 
    if (i == 2) 
     queue.add(42); 

    System.out.println(i); 
} 

結果如下:

1 
2 
3 
4 
42 

正如希望的那樣,當我們擊中其中加入元素42出現了2

1

使用迭代器...不,我不這麼認爲。你必須破解起來是這樣的:

Collection<String> collection = new ArrayList<String>(Arrays.asList("foo", "bar", "baz")); 
    int i = 0; 
    while (i < collection.size()) { 

     String curItem = collection.toArray(new String[ collection.size() ])[ i ]; 
     if (curItem.equals("foo")) { 
      collection.add("added-item-1"); 
     } 
     if (curItem.equals("added-item-1")) { 
      collection.add("added-item-2"); 
     } 

     i++; 
    } 

    System.out.println(collection); 

其中yeilds:
[富,酒吧,巴茲,增加項目-1,添加項-2]

0

我更喜歡處理收集功能,而不是將它們改變。這完全避免了這種問題,以及別名問題和其他棘手的錯誤來源。

所以,我會實現它想:

List<Thing> expand(List<Thing> inputs) { 
    List<Thing> expanded = new ArrayList<Thing>(); 

    for (Thing thing : inputs) { 
     expanded.add(thing); 
     if (needsSomeMoreThings(thing)) { 
      addMoreThingsTo(expanded); 
     } 
    } 

    return expanded; 
} 
0

恕我直言更安全的方式是創建一個新的集合,遍歷您定collection,添加新的集合中的每個元素,並添加額外的元素在新的收藏中也需要,最後返回新的收藏。

0

除了使用附加列表並調用addAll在迭代後插入新項目(例如用戶Nat的解決方案)的解決方案之外,還可以使用併發集合,如CopyOnWriteArrayList

的「快照」風格的迭代方法使用在迭代器創建的點的基準到所述陣列的狀態。這個數組在迭代器的生命週期中永遠不會改變,所以干擾是不可能的,迭代器保證不拋出ConcurrentModificationException。

有了這個特殊的集合(通常用於併發訪問),可以在迭代時操作基礎列表。但是,迭代器不會反映這些更改。

這是比其他解決方案更好嗎?可能不會,我不知道寫入時複製方法引入的開銷。

3

其實它很容易。只要想想最佳的方式。 我beleive的最佳方式是:

for (int i=0; i<list.size(); i++) { 
    Level obj = list.get(i); 

    //Here execute yr code that may add/or may not add new element(s) 
    //... 

    i=list.indexOf(obj); 
} 

下面的例子完美地工作在最合理的情況下 - 當你不需要重複迭代元素之前添加的新元素。關於迭代元素後面添加的元素 - 您可能不希望迭代它們。在這種情況下,您應該簡單地添加/或擴展帶有標記的yr對象,以標記它們不會迭代它們。

1
public static void main(String[] args) 
{ 
    // This array list simulates source of your candidates for processing 
    ArrayList<String> source = new ArrayList<String>(); 
    // This is the list where you actually keep all unprocessed candidates 
    LinkedList<String> list = new LinkedList<String>(); 

    // Here we add few elements into our simulated source of candidates 
    // just to have something to work with 
    source.add("first element"); 
    source.add("second element"); 
    source.add("third element"); 
    source.add("fourth element"); 
    source.add("The Fifth Element"); // aka Milla Jovovich 

    // Add first candidate for processing into our main list 
    list.addLast(source.get(0)); 

    // This is just here so we don't have to have helper index variable 
    // to go through source elements 
    source.remove(0); 

    // We will do this until there are no more candidates for processing 
    while(!list.isEmpty()) 
    { 
     // This is how we get next element for processing from our list 
     // of candidates. Here our candidate is String, in your case it 
     // will be whatever you work with. 
     String element = list.pollFirst(); 
     // This is where we process the element, just print it out in this case 
     System.out.println(element); 

     // This is simulation of process of adding new candidates for processing 
     // into our list during this iteration. 
     if(source.size() > 0) // When simulated source of candidates dries out, we stop 
     { 
      // Here you will somehow get your new candidate for processing 
      // In this case we just get it from our simulation source of candidates. 
      String newCandidate = source.get(0); 
      // This is the way to add new elements to your list of candidates for processing 
      list.addLast(newCandidate); 
      // In this example we add one candidate per while loop iteration and 
      // zero candidates when source list dries out. In real life you may happen 
      // to add more than one candidate here: 
      // list.addLast(newCandidate2); 
      // list.addLast(newCandidate3); 
      // etc. 

      // This is here so we don't have to use helper index variable for iteration 
      // through source. 
      source.remove(0); 
     } 
    } 
} 
0

由於要遍歷列表List<Object>,易於peasy的方法是:

while (!list.isEmpty()){ 
    Object obj = list.get(0); 

    // do whatever you need to 
    // possibly list.add(new Object obj1); 

    list.remove(0); 
} 

所以,你遍歷一個列表,始終走的第一個元素,然後將其移除。這樣,您可以在迭代時將新元素附加到列表中。

0

忘記迭代器,它們不適用於添加,僅用於刪除。我的答案只適用於名單,所以不要懲罰我不解決集合問題。堅持的基本知識:

List<ZeObj> myList = new ArrayList<ZeObj>(); 
    // populate the list with whatever 
      ........ 
    int noItems = myList.size(); 
    for (int i = 0; i < noItems; i++) { 
     ZeObj currItem = myList.get(i); 
     // when you want to add, simply add the new item at last and 
     // increment the stop condition 
     if (currItem.asksForMore()) { 
      myList.add(new ZeObj()); 
      noItems++; 
     } 
    } 
0

我累的ListIterator,但它並沒有幫助我的情況下,你必須使用列表,同時增加它。這裏有什麼適合我:

使用LinkedList

LinkedList<String> l = new LinkedList<String>(); 
l.addLast("A"); 

while(!l.isEmpty()){ 
    String str = l.removeFirst(); 
    if(/* Condition for adding new element*/) 
     l.addLast("<New Element>"); 
    else 
     System.out.println(str); 
} 

這可能會導致異常或運行到無限循環。然而,正如你所提到的

我敢肯定它不會在我的情況

檢查角落的情況下在這樣的代碼是你的責任。

0

這是我平時做,有收藏的套裝:

Set<T> adds = new HashSet<T>, dels = new HashSet<T>; 
for (T e: target) 
    if (<has to be removed>) dels.add (e); 
    else if (<has to be added>) adds.add (<new element>) 

target.removeAll (dels); 
target.addAll (adds); 

這造成一些額外的存儲器(中間套的指針,但沒有重複的元素髮生)和特步(迭代一遍又一遍但通常情況下這並不是什麼大問題,它可能比使用初始集合副本更好。

1

對於examle我們有兩個列表:

public static void main(String[] args) { 
     ArrayList a = new ArrayList(Arrays.asList(new String[]{"a1", "a2", "a3","a4", "a5"})); 
     ArrayList b = new ArrayList(Arrays.asList(new String[]{"b1", "b2", "b3","b4", "b5"})); 
     merge(a, b); 
     a.stream().map(x -> x + " ").forEach(System.out::print); 
    } 
    public static void merge(List a, List b){ 
     for (Iterator itb = b.iterator(); itb.hasNext();){ 
      for (ListIterator it = a.listIterator() ; it.hasNext() ;){ 
       it.next(); 
       it.add(itb.next()); 

      } 
     } 

    } 

A1 B1 A2 B2 A3 B3 A4 B4 A5 B5

0

即使我們不能迭代過程中添加項目到相同的列表,我們可以使用Java 8點的flatMap,將新元素添加到流中。這可以在一個條件下完成。在此之後,可以處理添加的項目。

這裏是一個Java示例示出了如何添加到正在進行的流根據條件,然後將其與一個條件處理的對象:

List<Integer> intList = new ArrayList<>(); 
intList.add(1); 
intList.add(2); 
intList.add(3); 

intList = intList.stream().flatMap(i -> { 
    if (i == 2) return Stream.of(i, i * 10); // condition for adding the extra items 
    return Stream.of(i); 
}).map(i -> i + 1) 
     .collect(Collectors.toList()); 

System.out.println(intList); 

玩具實例的輸出是:

[2,3,21,4]

0

ListIterator使用如下:

List<String> l = new ArrayList<>(); 
l.add("Foo"); 
ListIterator<String> iter = l.listIterator(l.size()); 
while(iter.hasPrevious()){ 
    String prev=iter.previous(); 
    if(true /*You condition here*/){ 
     iter.add("Bah"); 
     iter.add("Etc"); 
    } 
} 

關鍵是要迭代反向順序 - 然後添加的元素出現在下一次迭代中。