22

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

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

  • 如果 myA在集合内,请更新其字段b以匹配我的对象。
  • 否则,将其添加到集合中。

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

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

尤瓦尔=8-)

编辑:谢谢大家的回答......不幸的是,我不能在这里“接受”最好的答案,那些建议使用 a 的人Map,因为仅仅为了这个目的而彻底改变集合的类型会有点极端(这个集合是已经通过 Hibernate 映射...)

4

7 回答 7

23

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

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

于 2008-10-06T17:03:25.920 回答
13

你真的想使用 a Map<Integer,A>,而不是 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如果不是这种情况,实际上只使用 aMap<Integer,B>就是您想要的,那么它就会崩溃:

Map<Integer,B> map = new HashMap<Integer,B>();
// The insert-or-update is just this:
map.put( id, b );
于 2008-10-06T17:03:42.673 回答
10

如果您使用的是 Set,我认为您不会比使用 remove/add 更容易。

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

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

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

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

于 2008-10-06T18:54:37.380 回答
5

还没有人提到这一点,但基于hashCodeequals基于可变属性是您不应该做的那些非常非常大的事情之一。离开构造函数后,不要乱搞对象标识——这样做会大大增加你在路上遇到真正难以解决的错误的机会。即使您没有遇到错误,会计工作以确保您始终正确更新任何和所有依赖equalshashCode保持一致的数据结构将远远超过能够仅更改对象 id 的任何感知好处当你跑步时。

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

于 2008-10-07T03:33:59.750 回答
2

Map <A,A> 我知道它是多余的,但我相信它会让你得到你想要的行为。真的,我很想看到 Set 上有一个 get(Object o) 方法。

于 2008-10-06T18:19:46.733 回答
0

这有点超出范围,但您忘记重新实现 hashCode()。当您覆盖 equals 时,请覆盖 hashCode(),即使在示例中也是如此。

例如; 当您有 Set 的 HashSet 实现时, contains() 很可能会出错,因为 HashSet 使用 Object 的 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); 
  }
}

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

于 2008-10-06T17:33:21.040 回答
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)。

于 2008-10-06T18:42:29.480 回答