Как безопасно удалить другие элементы из Коллекции во время итерации по Коллекции - PullRequest
6 голосов
/ 26 мая 2009

Я выполняю итерацию над JRE Collection, который применяет концепцию итератора с отказоустойчивым быстродействием, и, таким образом, выдает ConcurrentModificationException, если Collection изменяется во время итерации, кроме использования метода Iterator.remove(). Однако мне нужно удалить «логического партнера» объекта, если объект соответствует условию. Таким образом предотвращая обработку партнера. Как я могу это сделать? Возможно, используя для этого лучший тип коллекции?

Пример.

myCollection<BusinessObject>

for (BusinessObject anObject : myCollection) 
{ 
  if (someConditionIsTrue) 
  { 
    myCollection.remove(anObjectsPartner); // throws ConcurrentModificationException 
  }
}

Спасибо.

Ответы [ 7 ]

9 голосов
/ 26 мая 2009

Это не вина коллекции, это то, как вы ее используете. Изменение коллекции в середине итерации приводит к этой ошибке (что хорошо, поскольку итерацию в общем случае невозможно продолжить однозначно).

Редактировать : Перечитав вопрос, этот подход не сработает, хотя я оставлю его здесь в качестве примера того, как избежать этой проблемы в общем случае.

То, что вы хотите, выглядит примерно так:

for (Iterator<BusinessObject> iter = myCollection.iterator; iter.hasNext(); )
{
    BusinessObject anObject = iter.next();
    if (someConditionIsTrue) 
    { 
        iter.remove();
    }        
}

Если вы удаляете объекты через сам итератор, он знает об удалении, и все работает так, как вы ожидаете. Обратите внимание, что хотя я думаю, что все стандартные коллекции работают в этом отношении хорошо, итераторы не обязаны требовать реализации метода remove (), поэтому, если у вас нет контроля над классом myCollection (и, следовательно, классом реализации возвращенного итератора) вам может понадобиться поставить больше проверок безопасности.

Альтернативный подход (скажем, если вы не можете гарантировать, что итератор поддерживает remove() и вам требуется эта функциональность), это создать копию коллекции для итерации, а затем удалить элементы из оригинальная коллекция.

Редактировать : Вероятно, вы можете использовать эту последнюю технику для достижения того, чего хотите, но затем вы все равно вернетесь к причине, по которой итераторы вначале выдают исключение: Что следует итерацию делать, если вы удалите элемент, который еще не достиг ? Удаление (или нет) текущего элемента относительно четко определено, но вы говорите об удалении партнера текущего элемента , который, как я полагаю, мог бы быть в произвольной точке итерируемого элемента. Поскольку нет четкого способа, которым это должно быть обработано, вам нужно будет самостоятельно предоставить некоторую форму логики, чтобы справиться с этим. В этом случае я бы склонялся к созданию и заполнению новой коллекции во время итерации, а затем присваивал ее переменной myCollection в конце. Если это невозможно, отслеживание элементов партнера, которые необходимо удалить, и вызов myCollection.removeAll будет правильным решением.

8 голосов
/ 26 мая 2009

Вы хотите удалить элемент из списка и продолжить итерацию в том же списке. Можете ли вы реализовать двухэтапное решение, когда на шаге 1 вы собираете элементы, подлежащие удалению, во временной коллекции, а на шаге 2 удаляете их после идентификации?

3 голосов
/ 26 мая 2009

Некоторые мысли (это зависит от того, какие именно отношения существуют между двумя объектами в коллекции):

  1. Карта с объектом в качестве ключа и партнером в качестве значения.
  2. CopyOnWriteArrayList, но вы должны заметить, когда вы нажмете партнера
  3. Сделайте копию в другом объекте Collection и итерируйте один, удаляя другой. Если эта оригинальная коллекция может быть набором, это наверняка поможет при удалении.
1 голос
/ 27 мая 2009

CopyOnWriteArrayList будет делать то, что вы хотите.

1 голос
/ 26 мая 2009

Вы можете попытаться сначала найти все элементы, которые нужно удалить, а затем удалить их, как только закончите обработку всего списка. Пропуск удаленных элементов по мере их обнаружения.

myCollection<BusinessObject>
List<BusinessObject> deletedObjects = new ArrayList(myCollection.size());

for (BusinessObject anObject : myCollection) 
{ 
  if (!deletedObjects.contains(anObject))
  {
      if (someConditionIsTrue) 
      { 
          deletedObjects.add(anObjectsPartner);
      }
  }
}
myCollection.removeAll(deletedObjects);
0 голосов
/ 27 мая 2009

Лучший ответ - второй, используйте итератор.

0 голосов
/ 26 мая 2009

Почему бы не использовать коллекцию всех исходных BusinessObject, а затем отдельный класс (например, Map), который их связывает (т.е. создает партнера)? Поместите их как составные элементы в свой собственный класс, чтобы вы всегда могли удалить Партнера при удалении бизнес-объекта. Не берите на себя ответственность вызывающего абонента каждый раз, когда ему нужно удалить BusinessObject из Коллекции.

IE

class BusinessObjectCollection implements Collection<BusinessObject> {
  Collection<BusinessObject> objects;
  Map<BusinessObject, BusinessObject> associations;

 public void remove(BusinessObject o) {
  ...
// remove from collection and dissasociate...
 }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...