В последнее время я был полностью ошеломлен странной ошибкой в моем коде и дошел до крайнего разочарования. В конце концов я пошел и поместил в System.out.println();
в моем коде, пока я не сузил его, чтобы выявить самые причудливые результаты. Вот код:
for (Iterator<Integer> it = list.iterator(); it.hasNext();) {
i = it.next();
System.out.println(DeathWish.getInstanceListReference()
.get(DeathWish.getInstanceListReference().size() -1) );
System.out.println("Golden number: " +
(DeathWish.getInstanceListReference().size() -1) );
System.out.println((DeathWish.getInstanceListReference().size() -1) == i);
System.out.println("CurrentInstance List: " +
Arrays.toString(DeathWish.getInstanceListReference().toArray()));
System.out.println("Iteration: " + i);
try {
System.out.println( DeathWish.getInstanceListReference()
.remove((int)list.get(i))); //remove unwanted objects from game paint list
} catch(IndexOutOfBoundsException ex) {
System.err.println(ex + " Error with multiple GameObj objects");
} finally {
System.out.println("\n");
}
}
и выход:
DeathWish.Bullet@ddd5de
Golden number: 2
true
CurrentInstance List: [DeathWish.Player@3a5cf7, DeathWish.Bullet@6cca54, DeathWish.Bullet@ddd5de]
Iteration: 2
java.lang.IndexOutOfBoundsException: Index: 2, Size: 2 Error with multiple GameObj objects
getInstanceListReference()
возвращает ArrayList<GameObj>
. GameObj
- один из супер-супер-классов в моем проекте, который представляет Game Objects. Кроме того, ArrayList
который возвращает getInstanceListReference()
не изменяется внутренним исходным кодом во время выполнения.
Существует код, который выполняется перед циклом for:
System.out.println("\n------------------------\n" + "Read Instance List: " + Arrays.toString(tmpLock.toArray()) + "|" + Arrays.toString(tmpLock1.toArray()));
System.out.println("Recycle Bin: " + recycleBin.get(0) + "|" + recycleBin.get(1) + "\n------------------------\n");
Этот список в исходном коде в самом верху относится к recycleBin
. Он содержит индексы объектов, которые планируется удалить. Цикл for с IndexOutOfBoundsException
перебирает целые числа в recycleBin
и использует их для удаления индексов в списке экземпляров.
Имею ли я какую-то плохую практику, о которой я не знаю, это вызывает проблему?
Без SSCCE, который мы можем запустить сами, мы не можем дать вам ответ наверняка. Однако на основе описания и комментариев, которые вы опубликовали, вот одно из возможных объяснений, которое приходит на ум:
Вы одновременно изменяете список, который вы выполняете, таким образом, чтобы не запускать исключение ConcurrentModificationException. Несмотря на название, вполне возможно столкнуться с такой проблемой даже в одном потоке; вот простой пример (SSCCE, если хотите):
int len = ls.size();
for(int i = 0; i < len; i++) {
if(i %2 == 0) {
ls.remove(i);
}
}
Поскольку это изменяет список во время итерации по нему, i
отсоединяюсь от элементов списка. Вместо того, чтобы удалять четные элементы, как может быть предполагаемое поведение, это приведет к удалению 0-го, 3-го, 6-го и т.д., IndexOutOfBoundException
он не IndexOutOfBoundException
частично через цикл.
Как правило, правильный способ избежать этой проблемы - перебрать одну коллекцию, изменить копию или работать с Iterator
на время операций удаления. Например, это безопасный способ удалить все остальные элементы - обратите внимание, что вместо работы с индексами мы работаем исключительно с итератором, который внутренне сохраняет свою позицию в списке независимо от того, что удалено:
Iterator<?> iter = ls.iterator();
while(iter.hasNext()) {
iter.next();
iter.remove();
if(iter.hasNext()) {
iter.next();
}
}
Хорошо, давайте попробуем другой пример, основанный более непосредственно на коде в вашем цикле.
У меня есть список указателей, которые я хотел бы удалить из другого списка:
List<Integer> toRemove = Arrays.asList(0,2,4,6);
List<String> myList = new ArrayList<>(
Arrays.asList("A","B","C","D","E","F","G","H"));
Теперь, если я попытаюсь удалить idexes 0, 2, 4 и 6 из myList
, я получу myList
IndexOutOfBoundsException
потому что, когда я вызываю .remove()
список сжимается на единицу, а все элементы, находящиеся позже в списке, перемещаются вниз.
for(int i : toRemove) {
myList.remove(i);
}
Хотя я не могу точно сказать, что подходит для вашего случая использования, один из вариантов заключается в том, чтобы оставить исходный список неповрежденным, изменить копию, а затем сохранить эту измененную копию после того, как все изменения были сделаны. Вот один из способов сделать это:
Set<Integer> toRemoveSet = new HashSet<>(toRemove);
List<String> cleanList = new ArrayList<>();
for(int i = 0; i < myList.size(); i++) {
if(!toRemoveSet.contains(i)) {
cleanList.add(myList.get(i));
}
}
System.out.println(cleanList);
Вместо того, чтобы удалять элементы один за другим, мы проверяем, должен ли поддерживаться каждый индекс или нет, и пропустите все, что не должно. Это позволяет избежать непоследовательного состояния попыток удалить элементы из списка, который уменьшается при работе с ним.
В общем, изменчивые коллекции могут быть сложными для работы, поскольку проекты растут по размеру. Отличная библиотека Guava предоставляет набор непреложных коллекций, специально предназначенных для того, чтобы помочь людям избежать подобных проблем. Это требует немного другого способа мышления о решении проблем, но это гораздо более надежный способ. Даже если вы не используете Guava, считайте свои коллекции неизменными, когда это возможно, и создавайте защитные копии при попытке структурных изменений.
Я бы переписал accept()
чтобы использовать методы библиотеки Java, доступные для работы с коллекциями.
accept()
передается Collection<Integer>
который представляет индексы, которые вы хотите удалить из DeathWith.getInstanceListReference()
. Подумайте, используя Collections.removeAll()
чтобы выполнить то, что (я считаю) вам нужно.
public void accept(Set<Integer> deadIndices) {
for (int i : deadIndices) {
DeathWish.getInstanceListReference().set(i, null);
}
List<GameObj> nullList = new ArrayList<GameObj>();
nullList.add(null);
DeathWish.getInstanceListReference().removeAll(nullList);
}
Это предполагает:
null
- недопустимое значение в DeathWish.getInstanceListReference()
.null
или любого другого значения часового типа также может привести к поломке - если список уже содержит null
, у вас возникнут проблемы. В конечном счете, использование .removeAll()
в качестве прокси-сервера для удаления определенных индексов похоже на использование отвертки в качестве молотка - все будет работать нормально, пока вы не .removeAll()
.
Свойство IndexOutOfBoundsException
исходит из вашего list.get(i)
который, как я предполагаю, имеет только 2 элемента, а не из DeathWish.getInstanceListReference()
!
list.get(i)
в строке 291, потому что в ней всего два элемента (2, 1), но вы пытаетесь прочитать третий элемент. Это не ошибка с Java, это ошибка с вашим кодом.