Java 更新 Set 中的对象
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/175186/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Updating an object within a Set
提问by Yuval
Let's say I have this type in my application:
假设我的应用程序中有这种类型:
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 }
}
and a Set
structure. Now, I have an object of type <A
>A
and want to do the following:
和一个结构。现在,我有一个类型的对象并希望执行以下操作:Set
<A
>A
- If my
A
is within the set, update its fieldb
to match my object. - Else, add it to the set.
- 如果我
A
在集合内,更新它的字段b
以匹配我的对象。 - 否则,将其添加到集合中。
So checking if it is in there is easy enough (contains
), and adding to the set is easy too. My question is this: how do I get a handle to update the object within? Interface Set
doesn't have a get
method, and the best I could think of was to remove the object in the set and add mine. another, even worse, alternative is to traverse the set with an iterator to try and locate the object.
所以检查它是否在那里很容易(contains
),并且添加到集合中也很容易。我的问题是:如何获得更新对象的句柄?接口Set
没有get
方法,我能想到的最好的方法是删除集合中的对象并添加我的。另一种更糟糕的替代方法是使用迭代器遍历集合以尝试定位对象。
I'll gladly take better suggestions... This includes the efficient use of other data structures.
我很乐意接受更好的建议......这包括有效使用其他数据结构。
Yuval =8-)
尤瓦尔 =8-)
EDIT: Thank you all for answering... Unfortunately I can't 'accept' the best answers here, those that suggest using a Map
, because changing the type of the collection radically for this purpose only would be a little extreme (this collection is already mapped through Hibernate...)
编辑:谢谢大家的回答......不幸的是,我不能在这里“接受”最好的答案,那些建议使用 a 的答案Map
,因为为此目的从根本上改变集合的类型只会有点极端(这个集合是已经通过 Hibernate 映射...)
回答by Paul Tomblin
Since a Set can only contain one instance of an object (as defined by its equals and hashCode methods), just remove it and then add it. If there was one already, that other one will be removed from the Set and replaced by the one you want.
由于 Set 只能包含对象的一个实例(由它的 equals 和 hashCode 方法定义),只需删除它然后添加它。如果已经有一个,另一个将从 Set 中删除并替换为您想要的那个。
I have code that does something similar - I am caching objects so that everywhere a particular object appears in a bunch of different places on the gui, it's always the same one. In that case, instead of using a Set I'm using a Map, and then I get an update, I retrieve it from the Map and update it in place rather than creating a new instance.
我有执行类似操作的代码 - 我正在缓存对象,以便特定对象出现在 gui 上的一堆不同位置的任何地方,它总是相同的。在这种情况下,我使用的是 Map,而不是使用 Set,然后我得到一个更新,我从 Map 中检索它并就地更新它,而不是创建一个新实例。
回答by Jason Cohen
You really want to use a Map<Integer,A>
, not a Set<A>
.
你真的想使用 a Map<Integer,A>
,而不是 a Set<A>
。
Then map the ID (even though it's also stored in A
!) to the object. So storing new is this:
然后将 ID(即使它也存储在A
!)映射到对象。所以存储 new 是这样的:
A a = ...;
Map<Integer,A> map = new HashMap<Integer,A>();
map.put( a.id, a );
Your complete update algorithm is:
您的完整更新算法是:
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;
}
However, it might be even simpler.I'm assuming you have more fields than that in A
that what you gave. If this is not the case, just using a Map<Integer,B>
is in fact what you want, then it collapses to nothing:
然而,它可能更简单。我假设你有比A
你给的更多的领域。 如果不是这种情况,Map<Integer,B>
实际上只是使用 a就是你想要的,那么它就会崩溃:
Map<Integer,B> map = new HashMap<Integer,B>();
// The insert-or-update is just this:
map.put( id, b );
回答by extraneon
It's a bit outside scope, but you forgot to re-implement hashCode(). When you override equals please override hashCode(), even in an example.
它有点超出范围,但您忘记重新实现 hashCode()。当您覆盖 equals 时,请覆盖 hashCode(),即使在示例中也是如此。
For example; contains() will very probably go wrong when you have a HashSet implementation of Set as the HashSet uses the hashCode of Object to locate the bucket (a number which has nothing to do with business logic), and only equals() the elements within that bucket.
例如; 当您有 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);
}
}
EDITYuvalindicated correctly that Set.add does not overwrite an existing element, but only adds if the element is not yet in the collection (with "is" implemented by equals)
编辑Yuvalindiced 正确指出 Set.add 不会覆盖现有元素,而只会在元素尚未在集合中时添加(“is”由 equals 实现)
回答by extraneon
What about Map<A,A
> I know it's redundant, but I believe it will get you the behavior you'd like. Really I'd love to see Set have a get(Object o) method on it.
Map 怎么样?<A,A
我知道它是多余的,但我相信它会让你得到你想要的行为。我真的很想看到 Set 有一个 get(Object o) 方法。
回答by Josh
You might want to generate a decorator called ASet and use an internal Map as the backing data structure
您可能想要生成一个名为 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);
}
}
You can also take a look at the Trove API. While that is better for performance and for accounting that you are working with primitive variables, it exposes this feature very nicely (e.g. map.adjustOrPutValue(key, initialValue, deltaValue).
您还可以查看 Trove API。虽然这对性能和计算您正在使用的原始变量更好,但它很好地公开了此功能(例如 map.adjustOrPutValue(key, initialValue, deltaValue)。
回答by 18Rabbit
I don't think you can make it any easier than using remove/add if you are using a Set.
如果您使用的是 Set,我认为您不会比使用 remove/add 更容易。
set.remove(a);
set.add(a);
If a matching A was found it will be removed and then you add the new one, you don't even need the if (set.contains(A))
conditional.
如果找到匹配的 A,它将被删除,然后您添加新的,您甚至不需要if (set.contains(A))
条件。
If you have an object with an ID and an updated field andyou don't really care about any other aspects of that object, just throw it out and replace it.
如果您有一个带有 ID 和更新字段的对象,而您并不真正关心该对象的任何其他方面,只需将其丢弃并替换即可。
If you need to do anything else to the A that matches that ID then you'll have to iterate through the Set to find it or use a different Container (like the Map as Jason suggested).
如果您需要对与该 ID 匹配的 A 执行任何其他操作,则您必须遍历 Set 以找到它或使用不同的容器(如 Jason 建议的 Map)。
回答by Kevin Day
No one has mentioned this yet, but basing hashcode or equals on a mutable property is one of those really, really big things that you shouldn't do. Don't muck about with object identity after you leave the constructor - doing so greatly increases your chances of having really difficult-to-figure out bugs down the road. Even if you don't get hit with bugs, the accounting work to make sure that you alwaysproperly update any and all data structures that relies on equals and hashcode being consistent will far outweigh any perceived benefits of being able to just change the id of the object as you run.
还没有人提到这一点,但是基于可变属性的哈希码或等号是您不应该做的非常非常重要的事情之一。离开构造函数后,不要考虑对象标识 - 这样做会大大增加您遇到真正难以解决的错误的机会。即使您没有遇到错误,确保您始终正确更新依赖于相等和哈希码一致的任何和所有数据结构的会计工作将远远超过能够仅更改 id 的任何感知好处运行时的对象。
Instead, I strongly recommend that you pass id in via the constructor, and if you need to change it, create a new instance of A. This will force users of your object (including yourself) to properly interact with the collection classes (and many others) that rely on immutable behavior in equals and hashcode.
相反,我强烈建议您通过构造函数传入 id,如果您需要更改它,请创建一个新的 A 实例。这将强制您的对象的用户(包括您自己)正确地与集合类(以及许多其他)依赖于 equals 和 hashcode 中的不可变行为。