Обновление объекта в наборе

Скажем, у меня этот тип в моем приложении:

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

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

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

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

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

Yuval = 8 -)

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

Ответы

Ответ 1

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

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

Ответ 2

Вы действительно хотите использовать Map<Integer,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 );

Ответ 3

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

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

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

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

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

Ответ 4

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

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

Ответ 5

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

Ответ 6

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

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

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" реализовано равным)

Ответ 7

Возможно, вы захотите создать декоратор под названием 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);
 }
}

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