1

Using an iterator and adding an element is not giving an error for the first example, where as the second example to add an element in the list while iterating through it gives an error. What could be the reason that it does not give any error in the former example but gives error in the latter ? Error throwing for second example - Cannot modify a collection while it is being iterated

Example 1:

List<Integer> addList = new List<Integer>();
addList.add(19);
addList.add(20);
addList.add(89);
addList.add(14);
Iterator<Integer> intIterator = addList.iterator();
while(intIterator.hasNext()){
    intIterator.next();
    if(intIterator.next()==2)
    {
        addList.add(1);
    }
}

Example 2:

List<Integer> addList = new List<Integer>();
addList.add(19);
addList.add(20);
addList.add(89);
addList.add(14);
for(integer i : addList){
    if(i==19){
        addList.add(1);
    }
}
3

I tried a simple experiment by changing your first example data from 2 into 20. So the code becomes:

intIterator.next();
if(intIterator.next()==20)
{
    addList.add(1);
}

Now it throws the same error as in Example 2.

So, the reason why Example 1 is not throwing error is quite simple now: it is because it NEVER really add(1) into addList, so the list is not modified.

  • That is correct Sir – manjit5190 Sep 23 '15 at 10:38
  • This does not provide an answer to the question. To critique or request clarification from an author, leave a comment below their post. – Bartley Sep 23 '15 at 15:09
  • @Bartley , I don't agree with you. The author is asking for a reason about why the behavior differs, not a solution on how to walk around this. I am not criticizing about the question, instead, I am trying to explain the cause. – Lance Shi Sep 23 '15 at 22:55
  • @Bartley it actually does provide the answer to the question. The question was What could be the reason that it does not give any error in the former example but gives error in the latter ? and the answer is: it is because it NEVER really add(1) into addList, so the list is not modified. – Novarg Jul 22 '16 at 8:09
2

As a workaround, you can create another List to add the Integers to during the iteration. You can then addAll the elements from the second list to the first after the iteration.

List<Integer> addList = new List<Integer>();
addList.add(19);
addList.add(20);
addList.add(89);
addList.add(14);

List<Integer addList2 = new List<Integer>();
for(integer i : addList){
    if(i==19){
        addList2.add(1);
    }
}

addList.addAll(addList2);
1

Short Answer: Because it is implemented that way.

Long (Not really long)

In the second case the for loop depends on the count of the elements in the list to determine how many times the loop should run(determined at start of loop). Hence if you add or remove elements, it will cause an inconsistency.

In the first case, the count is not determined, if you add a new element to a list, it will be added in the end of the list. The iterator only checks if the next element is there not. It does not calculates the count of elements at start as done in for loop.

Java Docs

Edit

Lance Shi's answer makes more sense in this context. Iterator will give you error in case of adding but not removing which is again termed as modifying the collection

  • This logic makes sense, for the same reason removing/adding in an iteration using the index number doesn't make much sense (it alters the index, and causes issues). – drakored Sep 23 '15 at 10:33
  • @Manjit_singh - why does it not give an error which removing ? (As you said it is also modifying the list) – xyz sfdc Sep 23 '15 at 10:52
  • 1
    I am guessing it is because you can remove only the current element which is in context, the list will still has next element to iterate upon(If its not the last element) however in case of add, if you are on the last element and you add new element, the iterator hasNext has already resulted in false but you have just added a new element so hasNext should be true. Now there is a state of inconsistency. Salesforce has not given implementation of Iterator.remove(). – manjit5190 Sep 23 '15 at 11:05
  • hmm... but in case of add I might not be at the last element .. in that case why would it throw error ? – xyz sfdc Sep 23 '15 at 11:35
  • 1
    The deeper answer is because Java throws IllegalStateException when doing add and some remove operations while iterating, and iterator.remove is the supported way to remove from a list while iterating. Java also has many more use cases to consider with their much larger set of subclasses of AbstractCollection. I think we pick up some side issues with apex being based on (or compiled to, etc) Java. With our minimal set of subclasses on that abstract, it doesn't immediately seem obvious why it functions that way. :shrugs: – drakored Sep 23 '15 at 12:03
0

First things first, your code as stated should throw an error for both examples, the first example doesn't for two reasons:

  • The if check is checking for '2' which doesn't appear to be any of the values and therefore won't ever trigger the add(..) call
  • intIterator.next(); does more than advance the iterator, it returns the next value. By calling it twice, the if check will only execute for every second value. Typically you should assign the value, e.g. int currentValue = intIterator.next(); so that it may be reused, or just use the for...each syntax, which does that for you behind the scenes.

The underlying class of exception is ConcurrentModificationException, in which the documentation states that the core issue here is a breach of contract. The problem is that the iterator promises not to make any changes to underlying Collection and that promise is enforced with checking, leading to your error.

There are ways around this. One way is to do a defensive copy before you iterate. As you're iterating over that copy, you can safely modify the original collection without causing an error, however, your iteration will be based on your initial collection and won't include any new items. E.g.

List<Integer> addList = new List<Integer>();
addList.add(19);
addList.add(20);
addList.add(89);
addList.add(14);

List<Integer> addListCopy = new List<Integer>(addList); // copy-constructor
for(Integer i : addListCopy)){ 
    if(i==19){
        addList.add(1);
    }
}

... for which the copy can be done inline like this:

for(Integer i : new List<Integer>(addList))){ 
    ...
}

Another approach to have a pair of lists and iteratively consume new values (which may then generate newer values) until no more new values are generated. This is useful if your new values need to be processed in the same manner as the original list, e.g.

List<Integer> addList = new List<Integer>();
addList.add(19);
addList.add(20);
addList.add(89);
addList.add(14);

List<Integer> newNumbers = new List<Integer>(addList); // prime with original numbers
List<Integer> currentNumbers;
while(!newNumbers.isEmpty()) {
    currentNumbers = new List<Integer>(newNumbers);
    newNumbers = new List<Integer>();
    for(Integer i : currentNumbers){
        if(i<=19){
            newNumbers.add(i-1);
        }
    }
}

An extreme approach, if you wanted to support parallel processing of each value and multiple threads adding new numbers arbitrarily across multiple threads, you might use a task queue with consumers which could safely enqueue further tasks, but I suspect this is beyond your needs.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.