英文:
Which problems can stem from overriding java.util.HashSets contains()-method?
问题
我想使用 HashSet
来存储一些对象:
public class StoredObject {
Type type; //Type 是一个枚举
//其他字段
Type getType() { return type; }
}
现在,我只想存储相同 Type
的一个 StoredObject
,因此我在 HashSet
的子类中重写了 contains()
方法:
public class MySet<E extends StoredObject> extends java.util.HashSet<E> {
@Override
public boolean contains(Object o) {
if (o instanceof StoredObject) {
for (StoredObject s : this) {
if (s.getType() == ((StoredObject) o).getType()) return true;
}
}
return false;
}
}
之前,我想使用 HashSet
并修改 StoredObject
的 equals()
方法。然而,上述方法似乎是一种更简洁且更安全的方式,尤其是在我所描述的情况下,存储的对象都实现了一个接口而不是扩展相同的类。
现在我的问题是:这个实现是安全的吗?我尝试搜索可能会出问题的地方,但没有找到任何信息。我了解到重写 equals()
可能会破坏集合。此外,这个子类是否使 HashSet
失去了作用,因为它没有使用 HashMap
来进行 contains()
操作?
英文:
I want to use a HashSet
to store some objects:
public class StoredObject{
Type type; //Type is an enum
//other fields
Type getType(){return type;}
}
Now, I want to store only one StoredObject
of the same Type
, so I override contains()
in a subclass of HashSet
:
public MySet<E extends StoredObject> extends java.util.HashSet<E>{
@Override
public boolean contains(Object o) {
if(StoredObject.class.isAssignableFrom(o.getClass())) {//if o implements StoredObject
for(StoredObject s : this) {
if(s.getType() == ((StoredObject) o).getType()) return true;
}
}
return false
}
}
Before this I wanted to use HashSet
and modify the equals()
of StoredObject
. However, the way above seems like a shorter and safer way, especially as in my case the stored objects all implement an interface and don't extend the same class.
Now my question: Is this implementation safe? I tried to search for things it could break, but did not find any. I read that overriding equals()
can break Collections.
Also, does this subclass defeats the purpose of an HashSet
, since it does not use the HashMap
for contains()
?
答案1
得分: 4
HashMap<Type, StoredObject>
是适合这种情况的集合。
如果你覆盖了 equals(Object)
方法,那么你也必须覆盖 hashCode
方法(最好还实现一下 Comparable
接口,也许还可以覆盖 toString
方法)。使用 @Override
注解可以确保参数类型和拼写正确 - 很容易出错,而且调试起来会很混乱。
可能出现的问题:
- 在
HashSet
中有许多需要覆盖的方法,所以工作量很大。 - 未来的 Java 版本可能会向
HashSet
中添加更多方法 - 你打算如何留意这一点呢? contains
应该是 O(1) 的操作(假设哈希码分布良好),但 OP 的实现是 O(n)。- 对另一个
Set
使用Set.equals
会报告不正确的结果。
另请注意,StoredObject.class.isAssignableFrom(o.getClass())
可以更好地写成 o instanceof StoredObject
(假设你已经正确地使用了 isAssignableFrom
)。
英文:
HashMap<Type,StoredObject>
is the appropriate collection for this.
If you override equals(Object)
then you must also override hashCode
(it's also not a bad idea to make it implement Comparable
and perhaps override toString
). Use the @Override
annotation to ensure you have the right parameter types and spelling - very easy to get wrong and confusing to debug.
What can go wrong?
- There's a lot of methods in
HashSet
to override, so that's a lot of work. - More methods may be added to
HashSet
in future versions of Java - how are you going to look out for this? contains
should be an O(1) operation (assuming a good distribution of hash codes), but the OP implementation is O(n).Set.equals
on anotherSet
will report incorrect results.
Note also that StoredObject.class.isAssignableFrom(o.getClass())
is better written as o instanceof StoredObject
(assuming you've got isAssignableFrom
the right way around).
答案2
得分: 1
这个实现安全吗?
绝对不安全。HashSet
上还有其他方法,如 add()
,它们不会正确工作,会导致集合的大小不正确。
此外,这个实现会彻底破坏 contains
方法的性能,使其从 O(1) 变成 O(n)。
如果您需要一个具有不同于对象默认定义的相等性定义的 Set
,可以使用 TreeSet
并提供自定义的 Comparator
。
class MySet<E extends StoredObject> extends java.util.TreeSet<E> {
public MySet() {
super(Comparator.comparing(StoredObject::getType));
}
}
我同意 Tom Hawtin - tackline 的观点,HashMap<Type, StoredObject>
是一个更好的选项,因为它允许您根据给定的 Type
获取 StoredObject
,而这在 Set
中很难实现。它还允许您仅根据 Type
进行存在性检查,而无需为检查创建虚拟的 StoredObject
对象。
英文:
> Is this implementation safe?
Absolutely not. There are other methods on HashSet
that wouldn't work correctly, e.g. add()
, leaving the size of the set incorrect.
Besides, that implementation would totally ruin the performance of the contains
method, making it run in O(n) instead of O(1).
If you need a Set
with a definition of equality that differs from the objects natural definition as implemented by equals()
and hashCode()
, use a TreeSet
and supply a custom Comparator
.
class MySet<E extends StoredObject> extends java.util.TreeSet<E> {
public MySet() {
super(Comparator.comparing(StoredObject::getType));
}
}
I do agree with Tom Hawtin - tackline, that HashMap<Type, StoredObject>
is a better option, because it allows you to get the StoredObject
for a given Type
, which is otherwise very difficult to do with a Set
. It also allows you to check for existence given just a Type
, without having to create a dummy StoredObject
object for the check.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论