can anyone explain this Java gotcha to me?
This little annoyance caused me to lose an hour of sleep, and I don't understand why.
I have an ArrayList array which I want to iterate over and conditionally remove items. This was my first attempt:
for (int i = 0; i < array.size(); i++) {
if (array.get(i) == conditionMet) array.remove(i);
}
And that did not work.开发者_运维知识库 The following did:
for (Iterator<T> i = array.iterator(); i.hasNext();) {
if (i.next() == conditionMet) i.remove();
}
Why?
You didn't specify how it didn't work but when you're iterating over the ArrayList
in the for
loop and removing the element at the current index i
, the size of the collection and indices for subsequent elements, both change which is probably what you were not expecting.
From the documentation for ArrayList#remove(int index)
:
Removes the element at the specified position in this list. Shifts any subsequent elements to the left (subtracts one from their indices).
What type is the ArrayList and conditionMet? You can't compare objects with == only with the method .equals (which you should override).
edit: (haven't done Java in a while and not 100% sure if this will be correct, you should test it)
Iterator<E> it = list.getIterator();
while(it.hasNext()) {
E obj = it.next();
if(obj.equals(VARIABLE)) {
it.remove();
}
}
The problem is that you're trying to change the list over which you're iterating. if you did something like this:
int listSize = array.size();
for (int i = 0; i < list; i++) {
if (array.get(i) == conditionMet) {
array.remove(i);
i--;
listSize--;
}
}
This way, you're just iterating up to an int, rather than using the size of an ArrayList that you're changing in the loop body, which Java doesn't allow.
Hope that works for you.
Simple. In the second case, i is a class of type iterator, and in the first, i is an Integer. When you remove the values, while iterating, the indices will change (basically, skip one). So each time you remove, you have to decrement i by one (which will be again incremented by the for loop). So, modifying the first one to
for (int i = 0; i < array.size(); i++) {
if (array.get(i) == conditionMet) array.remove(i--);
}
Collections don't like having members removed mid-loop except when it's done through an Iterator.
Iterators allow the caller to remove elements from the underlying collection during the iteration with well-defined semantics.
In the first for loop,
for (int i = 0; i < array.size(); i++) {
if (array.get(i) == conditionMet) array.remove(i);
}
the condition ; i < array.size();
is evaluated in the very beginning which will take the size of the original list. So while iterating over it, if you remove any element from it, the size has reduced, but you are still iterating up to the original size, which is obviously wrong.
In the for second loop,
for (Iterator<T> i = array.iterator(); i.hasNext();) {
if (i.next() == conditionMet) i.remove();
}
since you are using iterator, you are checking if there is anymore element is left in the list or not. hence you do not overstep and it works properly.
(You should have included the exception in the question. "Exception in thread "main" java.lang.IndexOutOfBoundsException")
精彩评论