2009-06-14 4 views
66

Можно ли добавлять элементы в коллекцию во время итерации по ней?Java: добавление элементов в коллекцию во время итерации

Более конкретно, я хотел бы перебирать коллекцию, и если элемент удовлетворяет определенному условию, я хочу добавить в коллекцию другие элементы и убедиться, что эти добавленные элементы также повторяются. (Я понимаю, что это мог привести к unterminating петли, но я уверен, что это будет не в моем случае.)

Java Tutorial от Солнца показывает, что это не представляется возможным: «Обратите внимание, что Iterator.remove является только безопасный способ изменения коллекции во время итерации, поведение неуказано, если базовая коллекция модифицируется каким-либо другим способом, пока выполняется итерация ».

Так что, если я не могу делать то, что хочу делать с помощью итераторов, что вы предлагаете мне делать?

ответ

52

Как насчет создания очереди с элементами, которые вы хотите перебрать; когда вы хотите добавить элементы, запишите их в конце очереди и продолжайте удалять элементы до тех пор, пока очередь не будет пустой. Так обычно работает поиск по ширине.

+1

Если кто-то хочет подробно остановиться на этой идее, не стесняйтесь ... – Avi

+2

Это хороший способ сделать что-то, если оно подходит для модели, которую программирует OP. Таким образом, вы не используете итератор - просто цикл while. в то время как в очереди есть элементы, обработайте первый элемент. Однако вы можете сделать это и со списком. – Eddie

+0

Не понимаю. – inetphantom

-1

общий, это небезопасно, хотя для некоторых коллекций это может быть. Очевидная альтернатива - использовать какой-то цикл for. Но вы не сказали, какую коллекцию вы используете, чтобы это было возможно или не возможно.

43

Есть две проблемы:

Первый вопрос, добавив к Collection после Iterator возвращаются. Как уже упоминалось, не существует определенное поведение, когда основной Collection изменяется, как указано в документации на Iterator.remove:

... Поведение итератора является неопределенные если основной набор изменяется в то время как итерация выполняется любым способом , кроме как путем вызова этого метода.

Вторая проблема, даже если Iterator может быть получена, а затем вернуться к тому же элементу, Iterator был в, нет никакой гарантии, о порядке iteratation, как указана в документации Collection.iterator метода:

... Там нет никаких гарантий относительно порядка, в котором элементы возвращается (если это собрание не является экземпляром некоторого класса, который обеспечивает гарантию).

Например, у нас есть список [1, 2, 3, 4].

Скажем 5 был добавлен, когда Iterator был 3, и каким-то образом, мы получаем 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 (first-in, first-out), этот заказ типичен. (Как отмечено в документации по интерфейсу Queue, это не необходимость в Queue. Возьмет случай PriorityQueue, который упорядочивает элементы по их естественному порядку, так что это не FIFO.)

Ниже приведен пример использования a LinkedList (который является Queue), чтобы пройти через все элементы вместе с дополнительными элементами, добавленными во время детекции. Подобно выше примере элемент 42 добавляется, когда элемент 2 удаляется:

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.

+0

Я думаю, что точка Ави заключалась в том, что если у вас есть очередь, Не нужно перебирать его. Вы просто удаляете элементы с фронта, пока он не пуст, и накладывайте новые элементы на спину. – Nat

+0

@Nat: Вы правы, спасибо, что указали это. Я отредактировал свой ответ, чтобы отразить это. – coobird

+1

@coobird По какой-то причине ваш ответ усечен. _ [...], чтобы пройти через все элементы вместе с дополнительными el-_, и это все, что я могу видеть, однако, если я попытаюсь отредактировать ответ, все будет там. Любая идея о том, что происходит? –

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:
[Foo, бар, Баз, добавленное пункт-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

ИМХО безопасный способ был бы создать новую коллекцию, перебрать вашу данную коллекцию, добавляя каждый элемент в новой коллекции, а также добавлять дополнительные элементы по мере необходимости в новой коллекции, наконец, вернув новую коллекцию.

0

Помимо решения использовать дополнительный список и вызывать addAll для вставки новых элементов после итерации (например, решение пользователем Nat), вы также можете использовать параллельные коллекции, такие как CopyOnWriteArrayList.

Метод итератора стиля «моментальный снимок» использует ссылку на состояние массива в точке, в которой был создан итератор. Этот массив никогда не изменяется в течение жизни итератора, поэтому помехи невозможны, и итератору гарантировано не бросать ConcurrentModificationException.

С помощью этой специальной коллекции (обычно используемой для одновременного доступа) можно манипулировать базовым списком, итерации по нему. Однако итератор не будет отражать изменения.

Это лучше, чем другое решение? Наверное, нет, я не знаю накладных расходов, введенных подходом Copy-On-Write.

3

На самом деле это довольно легко. Просто подумайте об оптимальном пути. я считаю оптимальным способом является:

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 с флагом, который будет отмечать их, чтобы не перебирать их.

+0

indexOf не требуется для добавления и может сбивать с толку, если у вас есть дубликаты. –

+0

Да, действительно, дубликаты - проблема. Thanx для добавления этого. – PatlaDJ

+0

Следует добавить, что в зависимости от реализации фактического списка list.get (i) может быть намного дороже, чем использование итератора. Может быть значительное снижение производительности, по крайней мере, для более крупных связанных списков, например. –

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

4-я строка должна читать «int noItems = myList.size()». –

+0

Спасибо Стефан. Починил это. –

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

Для нелогич- у нас есть два списка:

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

Ключ к итерации в обратном порядке - тогда добавленные элементы появятся на следующей итерации.