58

I understand that in Java a Collection<E> should not be modified while iterating through it, such as removing or adding elements. But what about changing the elements in a List? For example, what if we have

List<String> letters = new ArrayList<String>();
letters.add("A");
letters.add("B");
letters.add("C");
int i = 0;

for (String letter : letters) {
    letters.set(i, "D");
    i++;
}

So, I'm not talking about modifying the object stored at an element; I'm talking about changing what the object is. The size of the List is not being changed, but the object at the index is changing, so technically the List is being modified. My boss claims this code is fine (and it does appear to work), but I still don't think it is correct. Would a different way of doing it, maybe using the set(E e) method of a ListIterator, be better?

Yu Hao
  • 119,891
  • 44
  • 235
  • 294
user176410
  • 713
  • 1
  • 5
  • 6
  • 2
    why you `foreach` if you are not going to use the variable letter? Why don not try it to see if it works? – RamonBoza Dec 17 '13 at 16:13
  • have you tried it before asking this question? – PrR3 Dec 17 '13 at 16:14
  • http://stackoverflow.com/questions/6958478/modifying-a-collection-while-iterating-using-for-each-loop – SubSevn Dec 17 '13 at 16:14
  • 11
    This is stated in the doc : _"A structural modification is any operation that adds or deletes one or more elements, or explicitly resizes the backing array; merely setting the value of an element **is not** a structural modification."_ – Alexis C. Dec 17 '13 at 16:14
  • Thank you ZouZou. I found, "Note that Iterator.remove is the only safe way to modify a collection during iteration; the behavior is unspecified if the underlying collection is modified in any other way while the iteration is in progress." But your statement is the one I was looking for. I looked in javadoc for List, but it's not there. I see it in ArrayList. Thanks. – user176410 Dec 17 '13 at 16:58
  • Comment mentioned by @AlexisC. should be credited for Answer to this question, as simple it can get. – Abhijeet Aug 12 '16 at 02:16

4 Answers4

68

There is nothing wrong with the idea of modifying an element inside a list while traversing it (don't modify the list itself, that's not recommended), but it can be better expressed like this:

for (int i = 0; i < letters.size(); i++) {
    letters.set(i, "D");
}

At the end the whole list will have the letter "D" as its content. It's not a good idea to use an enhanced for loop in this case, you're not using the iteration variable for anything, and besides you can't modify the list's contents using the iteration variable.

Notice that the above snippet is not modifying the list's structure - meaning: no elements are added or removed and the lists' size remains constant. Simply replacing one element by another doesn't count as a structural modification. Here's the link to the documentation quoted by @ZouZou in the comments, it states that:

A structural modification is any operation that adds or deletes one or more elements, or explicitly resizes the backing array; merely setting the value of an element is not a structural modification

Óscar López
  • 232,561
  • 37
  • 312
  • 386
  • 6
    +1 If you use CopyOnWriteArrayList you can modify it while iterating over it. – Peter Lawrey Dec 17 '13 at 16:34
  • I understand I wasn't using the iteration variable. This was a simple example based on different code where the iteration variable is used. I was just trying to indicate that the list was changing during iteration. I could not easily find documentation for "structural modification" which is what I was looking for! Thank you and ZouZou for your answers. – user176410 Dec 17 '13 at 17:02
  • 1
    Bonus: if you iterate backwards, you can remove elements while iterating. – midrare Feb 15 '21 at 01:32
  • Note that `CopyOnWriteArrayList::set(int index, E element)` makes a copy of the list. This can lead to unwanted GC pressure and increase in runtime. – adambene Sep 19 '21 at 08:59
18

Use CopyOnWriteArrayList
and if you want to remove it, do the following:

for (Iterator<String> it = userList.iterator(); it.hasNext() ;)
{
    if (wordsToRemove.contains(word))
    {
        it.remove();
    }
}
degill
  • 1,285
  • 2
  • 13
  • 19
ThmHarsh
  • 601
  • 7
  • 7
  • 3
    This answer is deleting the item from the list which isn't the answer to the question. The question is how to replace/update/swap an item in the list while iterating. – buzz3791 Feb 15 '19 at 16:53
  • 3
    CopyOnWriteArrayList returns an iterator that does not support the remove() method. See https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CopyOnWriteArrayList.html#iterator() – L. Blanc Mar 27 '19 at 19:28
8

Java 8's stream() interface provides a great way to update a list in place.

To safely update items in the list, use map():

List<String> letters = new ArrayList<>();

// add stuff to list

letters = letters.stream().map(x -> "D").collect(Collectors.toList());

To safely remove items in place, use filter():


letters.stream().filter(x -> !x.equals("A")).collect(Collectors.toList());
Luke
  • 186
  • 1
  • 7
  • 12
    Writing *in place* into the description twice does not make any these lines doing in-place modification of a list. `Collectors.toList()` creates a brand new list for you, which is then stored in `letters` in the first example, and simply thrown away in the second one. – tevemadar Jan 15 '20 at 12:55
3

Use Java 8's removeIf(),

To remove safely,

letters.removeIf(x -> !x.equals("A"));
Alok
  • 361
  • 3
  • 4