views:

433

answers:

8

I'm iterating over a JRE Collection which enforces the fail-fast iterator concept. Problem is I need to remove an object's logical partner if the object meets a condition. Thus preventing the partner from also being processed. Can someone suggestion a better collection type for this purpose?

Example.

myCollection<BusinessObject>

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

Thanks.

+7  A: 

You want to remove an item from a list and continue to iterate on the same list. Can you implement a two-step solution where in step 1 you collect the items to be removed in an interim collection and in step 2 remove them after identifying them?

Alex B
+7  A: 

It's not a fault of the collection, it's the way you're using it. Modifying the collection while halfway through an iteration leads to this error (which is a good thing as the iteration would in general be impossible to continue unambiguously).

Edit: Having reread the question this approach won't work, though I'm leaving it here as an example of how to avoid this problem in the general case.

What you want is something like this:

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

If you remove objects through the Iterator itself, it's aware of the removal and everything works as you'd expect. Note that while I think all standard collections work nicely in this respect, Iterators are not required to implement the remove() method so if you have no control over the class of myCollection (and thus the implementation class of the returned iterator) you might need to put more safety checks in there.

An alternative approach (say, if you can't guarantee the iterator supports remove() and you require this functionality) is to create a copy of the collection to iterate over, then remove the elements from the original collection.

Edit: You can probably use this latter technique to achieve what you want, but then you still end up coming back to the reason why iterators throw the exception in the first place: What should the iteration do if you remove an element it hasn't yet reached? Removing (or not) the current element is relatively well-defined, but you talk about removing the current element's partner, which I presume could be at a random point in the iterable. Since there's no clear way that this should be handled, you'll need to provide some form of logic yourself to cope with this. In which case, I'd lean towards creating and populating a new collection during the iteration, and then assigning this to the myCollection variable at the end. If this isn't possible, then keeping track of the partner elements to remove and calling myCollection.removeAll would be the way to go.

Andrzej Doyle
I think you'll have to use the alternative approach because the object being removed is anObjectsPartner (not anObject) which I understand could occur earlier or later in the list.
Alex B
Yes, I just realised that and edited appropriately. It does make the situation more sticky...
Andrzej Doyle
+2  A: 

Some thoughts (it depends on what exactly the relationship is between the two objects in the collection):

  1. A Map with the object as the key and the partner as the value.
  2. A CopyOnWriteArrayList, but you have to notice when you hit the partner
  3. Make a copy into a different Collection object, and iterate over one, removing the other. If this original Collection can be a Set, that would certaily be helpful in removal.
Yishai
A: 

Why not use a Collection of all the original BusinessObject and then a separate class (such as a Map) which associates them (ie creates partner)? Put these both as a composite elements in it's own class so that you can always remove the Partner when Business object is removed. Don't make it the responsibility of the caller every time they need to remove a BusinessObject from the Collection.

IE

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

 public void remove(BusinessObject o) {
  ...
// remove from collection and dissasociate...
 }
}
GreenieMeanie
A: 

An example of @AlexB:

myCollection<BusinessObject>

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

myCollection will not error on removal of an object that is does not contain. However the Collection you are iterating over is a snapshot of the myCollection at the time that the ArrayList is constructed. If you are pruning myCollection periodically this should be fine.

Clint
A: 

You could try finding all the items to remove first and then remove them once you have finished processing the entire list. Skipping over the deleted items as you find them.

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);
Robin
A: 

CopyOnWriteArrayList will do what you want.

Javamann
A: 

The best answer is the second, use an iterator.