更新集合中的对象

时间:2008-10-06 16:58:30

标签: java data-structures set

假设我的应用程序中有这种类型:

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<A>结构。现在,我有一个<A类型的对象,并希望执行以下操作:

  • 如果我的A位于该集合中,请更新其字段A以匹配我的对象。
  • 否则,将其添加到集合中。

因此检查它是否在那里很容易(b),并且添加到集合也很容易。我的问题是:如何获得更新对象的句柄?接口contains没有Set方法,我能想到的最好的方法是删除集合中的对象并添加我的对象。另一个更糟糕的选择是使用迭代器遍历集合以尝试找到对象。

我很乐意接受更好的建议......这包括有效使用其他数据结构。

Yuval = 8 - )

编辑:谢谢大家的回答......不幸的是,我不能'接受'这里的最佳答案,建议使用get,因为改变了类型从根本上来说,为了这个目的,集合只会有点极端(这个集合已经通过Hibernate映射了......)

7 个答案:

答案 0 :(得分:17)

由于Set只能包含一个对象实例(由equals和hashCode方法定义),只需将其删除然后添加即可。如果已经存在,那么另一个将从Set中删除并替换为您想要的那个。

我有类似的代码 - 我正在缓存对象,以便在gui上的一堆不同位置出现特定对象,它始终是相同的。在这种情况下,不是使用Set我使用Map,然后我得到更新,我从Map中检索并更新它而不是创建新实例。

答案 1 :(得分:11)

您确实想要使用Map<int,A>,而不是Set<A>

然后将ID(即使它也存储在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 );

答案 2 :(得分:6)

如果您使用Set,我认为您不能比使用删除/添加更容易。

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

如果找到匹配的A,它将被删除,然后你添加新的,你甚至不需要if (set.contains(A))条件。

如果你有一个带有ID和更新字段的对象,你并不真正关心该对象的任何其他方面,只需将其抛出并替换它。

如果您需要对匹配该ID的A执行任何其他操作,则您必须遍历Set以查找它或使用其他Container(如Jason建议的Map)。

答案 3 :(得分:4)

还没有人提到这一点,但是基于hashcode或equals在一个可变属性上是你不应该做的真正,非常重要的事情之一。离开构造函数后,不要滥用对象标识 - 这样做会大大增加你很难找到错误的机会。即使您没有遇到错误,会计工作以确保您始终正确更新任何依赖于equals和hashcode一致的数据结构将远远超过任何感知的好处能够在你跑步时改变对象的id。

相反,我强烈建议您通过构造函数传递id,如果需要更改它,请创建一个新的A实例。这将强制您的对象(包括您自己)的用户与集合类正确交互(以及许多其他人)依赖于equals和hashcode中的不可变行为。

答案 4 :(得分:1)

地图<A,A&gt;怎么样?我知道这是多余的,但我相信它会让你得到你想要的行为。我真的很想看到Set上有一个get(Object o)方法。

答案 5 :(得分:0)

它有点超出范围,但是你忘了重新实现hashCode()。当你重写equals时,请覆盖hashCode(),即使在一个例子中也是如此。

例如;当你有一个Set的HashSet实现时,contains()很可能会出错,因为HashSet使用Object的hashCode来定位存储桶(一个与业务逻辑无关的数字),并且只有equals()中的元素桶。

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

编辑 Yuval正确表明Set.add不会覆盖现有元素,但只会在元素尚未包含在集合中时添加(“is”由equals实现)

答案 6 :(得分:0)

您可能希望生成一个名为ASet的装饰器,并使用内部Map作为后备数据结构

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)。