views:

145

answers:

3

I am iterating over, and modifying a map (which is created from an existing group of enum objects) like the following:

public class Dispenser {
   private Map<Ingredient, Integer> availableIngredients = 
         new EnumMap<Ingredient, Integer>(Ingredient.class);
   public void orderSandwich(SandwichType sandwichType) {
      Map<Ingredient, Integer> buffer = 
            new EnumMap<Ingredient, Integer>(availableIngredients);
      for (Map.Entry<Ingredient, Integer> entry : 
            sandwichType.getIngredients().entrySet()) {
         Integer currentUnits = buffer.get(entry.getKey());
         buffer.put(entry.getKey(), currentUnits - entry.getValue());
      }  
      availableIngredients.clear();
      availableIngredients.putAll(buffer);
   }
}

I wanted to ask if the temporary, method-local, buffer collection is necessary in this case. I mean, it works fine as it is but not sure its benefits. I have to clear my original collection and replace it with the contents of the buffer collection, which is basically the actual map that is modified within the loop.

Since, it works fine without the buffer collection (using only my original collection), I was wondering if one approach is recommended over the oter and why.

Many thanks for any advice on best practices on this.

+1  A: 

It's like to avoid ConcurrentModificationExceptions.

You cannot modify a collection while iterating over it, or such an exception will be thrown. What you have posted is a not-uncommon idiom for dealing with this - take a copy of the collection in one way or another, then you can iterate over one while modifying the other.

This can happen even in single-threaded code - for example, something like this will throw that exception on a collection with at least two elements:

for (Object o : myCollection)
{
   myCollection.remove(o);
}

An alternative, and likely more performant, way to deal with this is to explicitly declare the Iterator (rather than using the foreach loop), and then use the Iterator's remove method, if appropriate. (This doesn't apply to your case, though, because you're remapping rather than removing elements).

Edit: On reflection though, the availableIngredients map isn't being looped over so can simply be modified directly. You're right to be confused as it turns out. :-) Chances are this is vestigial from former refactoring, but it can be replaced by

public void orderSandwich(SandwichType sandwichType) {
  for (Map.Entry<Ingredient, Integer> entry : 
        sandwichType.getIngredients().entrySet()) {
     Integer currentUnits = availableIngredients.get(entry.getKey());
     availableIngredients.put(entry.getKey(), currentUnits - entry.getValue());
  }         
}

as you no doubt expect.

A thought that's just come to mind is that this might also have been a misguided attempt to make concurrency problems "less likely", by reducing the window of conflicting updates. Misguided, though, because threadsafety is absolute; making something ten times less likely to exhibit data races is not a good investment of time. It will still fail, "randomly", and is thus incorrect.

Andrzej Doyle
I didn't get. Where did he modify collection while iterating?
Mykola Golubyev
Thanks, will the ConcurrentModificationException be thrown by the collection even if concurrency is not addressed at all? I mean, my application is not multithreaded, so there couldn't be more than one thread operating on the same collection at the same time
denchr
I think there's some confusion here. The method is iterating over sandwichType.getIngredients() and modifying availableIngredients. Perhaps an earlier version was iterating and modifying the same collection, but I don't think it's necessary here.
Don Kirkby
-1 he's iterating over the sandwich ingredients and modifying the *available* ingredients.
Kevin Bourrillion
Yes, comprehension fail.
Andrzej Doyle
@Don Kirkby: Yes, you are right. I am iterating over a collection and actually modifying another - thanks for the clarification
denchr
+1  A: 

You don't need buffer map. You can operate on availableIngredients instead. Everything will be fine. With no useless overhead.

Mykola Golubyev
So what of the ConcurrentModificationException risk, mentioned in the previous answer?
denchr
Where did it happened?
Mykola Golubyev
I didn't happen, I am just trying understand the advantage of using a copy/buffer collection. I suppose in this case there is no advantage because there is no concurrent functionality
denchr
But then again, I imaging what dtsazza was referring to, was not two different threads operating on the collection at the same time, but rather only one thread would be doing two operations at the same time, meaning iterating and modifying at the same time
denchr
You don't iterate over availableIngredients so you can modify it.
Mykola Golubyev
Yes, I see that clearer now. Thanks, lesson learned
denchr
joel - it was misreading on my part - Mykola is right. The pattern *looks* like the was to avoid CMEs, but it turns out it isn't needed.
Andrzej Doyle
A: 

You do not have to use a buffer in this case, as you iterate over the collection sandwichType.getIngredients() while modifying availableIngredients these are separate collections.

For the cases where using a buffer is a good idea, replacing the original can be done easier like this:

  availableIngredients = buffer;

there is no need to update the original collection, it suffices to use the new version from now on.

rsp