Преимущество создания временного буфера при переборе и изменении коллекции в Java - PullRequest
1 голос
/ 18 ноября 2009

Я перебираю и изменяю карту (которая создается из существующей группы перечисляемых объектов), как показано ниже:

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);
   }
}

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

Поскольку он прекрасно работает без набора буферов (используя только мою исходную коллекцию), мне было интересно, рекомендуется ли какой-то один подход, а не почему?

Большое спасибо за любые советы по передовому опыту в этой области.

Ответы [ 3 ]

1 голос
/ 18 ноября 2009

Вам не нужна buffer карта. Вы можете работать на availableIngredients вместо этого. Все будет отлично. Без ненужных накладных расходов.

1 голос
/ 18 ноября 2009

Это все равно, что избежать ConcurrentModificationException s.

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

Это может произойти даже в однопоточном коде - например, что-то вроде этого вызовет это исключение в коллекции, по крайней мере, с двумя элементами:

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

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

Редактировать: хотя при отражении карта availableIngredients не зацикливается, поэтому ее можно просто изменить напрямую. Вы правы быть сбитым с толку, как оказалось. :-) Скорее всего, это след от прежнего рефакторинга, но его можно заменить на

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());
  }         
}

как вы, без сомнения, ожидаете.

Мысль, которая только что пришла в голову, состоит в том, что это, возможно, также была ошибочная попытка сделать проблемы параллелизма «менее вероятными», уменьшив окно конфликтующих обновлений. Однако ошибочно, потому что потокобезопасность абсолютна; Создание чего-то в десять раз менее вероятного для демонстрации гонок данных - не хорошее вложение времени. Он все равно будет «случайным образом» и неверен.

0 голосов
/ 18 ноября 2009

В этом случае вам не нужно использовать буфер, так как вы перебираете коллекцию sandwichType.getIngredients() при изменении availableIngredients это отдельные коллекции.

В тех случаях, когда использование буфера является хорошей идеей, заменить оригинал можно проще, например:

  availableIngredients = buffer;

Нет необходимости обновлять исходную коллекцию, достаточно использовать новую версию с этого момента.

...