Обновление объекта в наборе - PullRequest
16 голосов
/ 06 октября 2008

Допустим, у меня в приложении есть этот тип:

public class A {
  public int id;
  public B b;

  public boolean equals(Object another) { return this.id == ((A)another).id; }
  public int hashCode() { return 31 * id; //nice prime number }
}

и Set<code><A> структура. Теперь у меня есть объект типа A и я хочу сделать следующее:

  • Если мой A находится внутри набора, обновите его поле b, чтобы оно соответствовало моему объекту.
  • Иначе, добавьте его в набор.

Так что проверить, есть ли он там, достаточно просто (contains), и добавить в набор тоже легко. У меня такой вопрос: как мне получить дескриптор для обновления объекта внутри? Интерфейс Set не имеет метода get, и лучшее, что я мог придумать, это удалить объект из набора и добавить мой. другая, что еще хуже, альтернатива - перебрать множество с помощью итератора, чтобы попытаться найти объект.

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

Ювал = 8 -)

РЕДАКТИРОВАТЬ : Спасибо всем за ответы ... К сожалению, я не могу "принять" лучшие ответы здесь, те, которые предлагают использовать Map, потому что радикально изменить тип коллекции для эта цель была бы немного экстремальной (эта коллекция уже отображается через Hibernate ...)

Ответы [ 7 ]

17 голосов
/ 06 октября 2008

Поскольку Set может содержать только один экземпляр объекта (как определено его методами equals и hashCode), просто удалите его, а затем добавьте. Если он уже был, он будет удален из набора и заменен на тот, который вам нужен.

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

11 голосов
/ 06 октября 2008

Вы действительно хотите использовать Map<int,A>, а не Set<A>.

Затем сопоставьте идентификатор (хотя он также хранится в A!) С объектом. Итак, запоминание нового это:

A a = ...;
Map<Integer,A> map = new HashMap<Integer,A>();
map.put( a.id, a );

Ваш полный алгоритм обновления:

public static void update( Map<Integer,A> map, A obj ) {
  A existing = map.get( obj.id );
  if ( existing == null )
     map.put( obj.id, obj );
  else
     existing.b = obj.b;
}

Однако, это может быть даже проще. Я предполагаю, что у вас больше полей, чем в A, что вы дали. Если это не тот случай , просто использование Map<Integer,B> на самом деле то, что вы хотите, то оно рухнет в ничто:

Map<Integer,B> map = new HashMap<Integer,B>();
// The insert-or-update is just this:
map.put( id, b );
6 голосов
/ 06 октября 2008

Я не думаю, что вы можете сделать это проще, чем использовать remove / add, если вы используете Set.

    set.remove(a);
    set.add(a);

Если найден соответствующий A, он будет удален, а затем вы добавите новый, вам даже не нужно условное выражение if (set.contains(A)).

Если у вас есть объект с идентификатором и обновленными полями и , вы на самом деле не заботитесь о других аспектах этого объекта, просто выбросьте его и замените.

Если вам нужно сделать что-то еще для A, совпадающее с этим идентификатором, то вам придется перебирать Set, чтобы найти его или использовать другой Контейнер (например, Map, как предложил Джейсон).

4 голосов
/ 07 октября 2008

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

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

1 голос
/ 06 октября 2008

А как насчет карты <A,A> Я знаю, что это избыточно, но я верю, что это даст вам поведение, которое вы хотели бы. Очень хотелось бы, чтобы у Set был метод get (Object o).

0 голосов
/ 06 октября 2008

Возможно, вы захотите создать декоратор с именем ASet и использовать внутреннюю карту в качестве структуры данных поддержки

class ASet {
 private Map<Integer, A> map;
 public ASet() {
  map = new HashMap<Integer, A>();
 }

 public A updateOrAdd(Integer id, int delta) {
   A a = map.get(a);
   if(a == null) {
    a = new A(id);
    map.put(id,a);
   }
   a.setX(a.getX() + delta);
 }
}

Вы также можете взглянуть на Trove API. Хотя это лучше для производительности и для учета того, что вы работаете с примитивными переменными, он предоставляет эту функцию очень красиво (например, map.adjustOrPutValue (key, initialValue, deltaValue).

0 голосов
/ 06 октября 2008

Это немного выходит за рамки, но вы забыли повторно реализовать hashCode (). Когда вы переопределяете equals, пожалуйста, переопределите hashCode (), даже в примере.

Например; Метод Вектор (() очень вероятно пойдет не так, если у вас есть реализация Set для HashSet, поскольку HashSet использует hashCode объекта для определения местоположения (число, которое не имеет ничего общего с бизнес-логикой), и только равно () элементам в этом ведро.

public class A {
  public int id;
  public B b;
  public int hashCode() {return id;} // simple and efficient enough for small Sets 
  public boolean equals(Object another) { 
    if (object == null || ! (object instanceOf A) ) {
      return false;
    }
    return this.id == ((A)another).id; 
   }
}
public class Logic {
  /**
   * Replace the element in data with the same id as element, or add element
   * to data when the id of element is not yet used by any A in data. 
   */
  public void update(Set<A> data, A element) {
    data.remove(element); // Safe even if the element is not in the Set
    data.add(element); 
  }
}

РЕДАКТИРОВАТЬ Правильно указали, что Set.add не перезаписывает существующий элемент, а добавляет, только если элемент еще не находится в коллекции (с "is", реализованным равными)

...