Неожиданный IndexOutOfBoundsException

1

В последнее время я был полностью ошеломлен странной ошибкой в моем коде и дошел до крайнего разочарования. В конце концов я пошел и поместил в 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 и использует их для удаления индексов в списке экземпляров.

Имею ли я какую-то плохую практику, о которой я не знаю, это вызывает проблему?

  • 1
    Post SSCCE
  • 0
    Вы уверены, что не получаете доступ к этому списку из другого потока? Попробуйте добавить synchronized () для всех обращений к listReference, где он записан и прочитан. Это сильно пахнет ниткой проблемы.
Показать ещё 10 комментариев
Теги:
debugging
runtime-error
indexoutofboundsexception
iteration

3 ответа

5
Лучший ответ

Без 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, считайте свои коллекции неизменными, когда это возможно, и создавайте защитные копии при попытке структурных изменений.

  • 0
    также ваш пример отличается от моей «несогласованности» тем, что у меня был список ака. recycleBin сначала загружает самые большие индексы, чтобы при удалении это не повлияло на данные, стоящие за ним. В моем примере instanceList - это DeathWish.getInstanceListReference (), который возвращает ArrayList из GameObj.
  • 0
    Я собираюсь попробовать вашу технику iter.remove ()
Показать ещё 3 комментария
1

Я бы переписал 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().
  • 0
    Немного безопаснее, но использование null или любого другого значения часового типа также может привести к поломке - если список уже содержит null , у вас возникнут проблемы. В конечном счете, использование .removeAll() в качестве прокси-сервера для удаления определенных индексов похоже на использование отвертки в качестве молотка - все будет работать нормально, пока вы не .removeAll() .
  • 0
    Я думал об этом методе раньше: обнулил законченные игровые объекты и затем проверил, были ли они нулевыми, но я никогда не пробовал. Кроме того, почему вы столкнетесь с проблемами, если уже были нулевые значения? Разве удаление просто не удалит их?
Показать ещё 2 комментария
0

Свойство IndexOutOfBoundsException исходит из вашего list.get(i) который, как я предполагаю, имеет только 2 элемента, а не из DeathWish.getInstanceListReference() !

  • 0
    Ой, извините, забыл упомянуть, что этот список на самом деле мой «recycleBin». Он проверяет объекты в ArrayList instanceList и добавляет индекс объекта, который соответствует определенному условию. Вот еще вывод: Прочитать список экземпляров: [DeathWish.Player@a8409d, DeathWish.Bullet@1fcd8fd, DeathWish.Bullet@24f455] Корзина: [2, 1] | []. * Да, он был перекомпилирован с моими синхронизированными правками, но я все еще получил тот же другой вывод, включая ошибку.
  • 1
    Попытка получить третий объект из корзины вызывает проблему. list.get(i) в строке 291, потому что в ней всего два элемента (2, 1), но вы пытаетесь прочитать третий элемент. Это не ошибка с Java, это ошибка с вашим кодом.
Показать ещё 5 комментариев

Ещё вопросы

Сообщество Overcoder
Наверх
Меню