Impossible IndexOutOfBound happening on ArrayList.get(). Any clue?
I've got a very strange error, that would like to share with you.
I've the following code (simplified):
public ArrayList<String> al = new ArrayList<String>();
public void doSomething() {
int size = al.size();
for(int i=0; i<size; i++) {
if (al.get(i) != null) {
System.out.println(al.get(i));
String sPath = al.get(i);
File fFile = new File(sPath);
fFile.delete(); // Simplified. It has some error checking
}
}
}
I have one error, saw in the production environment:
java.lang.IndexOutOfBoundsException: Index: 1, Size: 0
at java.util.ArrayList.RangeCheck(Unknown Source)
at java.util.ArrayList.get(Unknown Source)
at MYCLASS.soSomething(MICLASS.java:1944)
[...]
Line 1944 is if (al.get(i) != null) {
.
What! How can it raise IndexOutOfBound?!
The problem is that the error does not reproduce. I've been able to raise it only once in the dev environment, but trying to reproduce it was not possible (it dit not raise again)... so no way to look for a pattern in the error.
So my only option is simple: read the code and use the brain.
So I browse the code of java.util.ArrayList.get()
:
pu开发者_开发技巧blic class ArrayList<E> [...]{
public E get(int index) {
RangeCheck(index);
return (E) elementData[index];
}
private void RangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
}
}
So it throws the exception because index >= size
... How is it possible? The for()
sweeps from 0
to size-1
... It is impossible! It cannot raise the error!
Wait a second! This code is not thread-safe. Maybe another thread is calling clear()
or remove*()
on the ArrayList... Studying the code, it is not possible. So I run it, set a breakpoint on line 1944 and watch the threads in that moment, and effectively, the other running threads have nothing to do with this issue.
Any other clue?
Kernnigan & Pike: "Debugging [...]Something impossible occurred, and the only solid information is that it really did occur."
I see the following candidates how this happens:
- something in your loop changes the value of
i
. - something in your loop changes the size of
al
, possibly something removing elements. - something is replacing your collection with a different collection.
- I have seen cases where the loop was constructed in a way so the body gets even executed once in the case of an empty collection. Although that seems unlikely with a for loop as described.
An idea for debugging this: replace the list with your own implementation which logs every access to it, and delegating all the real functionality to a standard implementation.
If needed it can print the stack trace of an freshly created exception in order to identify the place it is called from. It can even throw an exception when it gets accessed from a different thread.
Everyone is suggesting that you're removing elements from the Array. The solution to your problem is to use an Iterator
, which allows you to iterate through an entire Collection, but still allow you to modify that Collection.
You probably remove elements from your ArrayList
in your for loop, therefore decrementing its original size which is used to exit this loop!
How sure are you that your simplified code exactly replicates the production code? To my mind it's impossible for your simplified code to raise IndexOutOfBoundsException
because the list is empty so the for loop should never be processed. Are you modifying the contents of the array list in the loop?
Another possibility is that another thread is modifying the list while your code above is scanning it.
If you want something better than guesses, you need to show us either the relevant production code, or create a simplified version that actually exhibits the same problem when you run it.
Studying the code, it is not possible.
Clearly is is possible, otherwise you wouldn't have observed it.
Thanks everybody for the answers. Most of the suggestions are based on:
A) The loop is modifying al
or i
.
B) Another thread is modifying al
.
c) Another thread is replacing al
.
Since I know A is not the case, I'll bet that in some cases another thread is modifying/replacing al
. Since I cannot make it fail in a pattern, such thread may be running only in certain situations, such as error conditions. While the code in the loop is pretty simple, the application is not at all small, with abuse of singletons and getter/setters, and it hardens the debugging, so I wouldn't be surprised of it.
That's the same that I suspected (except C, that I had not though of), but you confirmed that this is the only possible cause. Thanks for your help.
精彩评论