覆盖 `java.util.HashSet` 的 `contains()` 方法可能引发哪些问题?

huangapple go评论73阅读模式
英文:

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 并修改 StoredObjectequals() 方法。然而,上述方法似乎是一种更简洁且更安全的方式,尤其是在我所描述的情况下,存储的对象都实现了一个接口而不是扩展相同的类。

现在我的问题是:这个实现是安全的吗?我尝试搜索可能会出问题的地方,但没有找到任何信息。我了解到重写 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&lt;E extends StoredObject&gt; extends java.util.HashSet&lt;E&gt;{
	@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&lt;Type,StoredObject&gt; 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 another Set 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&lt;E extends StoredObject&gt; extends java.util.TreeSet&lt;E&gt; {
	public MySet() {
		super(Comparator.comparing(StoredObject::getType));
	}
}

I do agree with Tom Hawtin - tackline, that HashMap&lt;Type, StoredObject&gt; 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.

huangapple
  • 本文由 发表于 2020年9月19日 22:56:23
  • 转载请务必保留本文链接:https://go.coder-hub.com/63970093.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定