重写java.util.HashSet的contains()方法:实现是否安全?是否违背初衷?
嘿,这个问题踩中了集合框架里一个很容易忽略的点——集合的方法是相互依赖的,不能只重写某一个方法就期望整体行为符合预期。咱们来一步步拆解问题:
为什么你的实现不安全?
HashSet的核心行为(add、remove、contains等)都是基于内部的HashMap实现的,而HashMap的判断逻辑依赖于equals()和hashCode()的契约,不是单独看contains()方法。你只重写了contains(),但add()方法依然会用默认的Object.equals()(即引用相等)来判断对象是否已存在,这会导致严重的行为不一致:
举个实际的例子:
StoredObject obj1 = new StoredObject(Type.A); StoredObject obj2 = new StoredObject(Type.A); MySet<StoredObject> set = new MySet<>(); set.add(obj1); set.add(obj2); // 因为obj1和obj2是不同引用,默认equals认为它们不等,所以会被成功添加 System.out.println(set.size()); // 输出2,但你期望的是每个Type只存一个 System.out.println(set.contains(obj2)); // 输出true,这时候集合里有两个同Type的对象,但contains却返回存在,完全矛盾
除此之外,remove()、containsAll()等方法也都会出现类似的不一致问题,因为它们都没有被你重写,依然遵循默认的equals逻辑。
为什么违背HashSet的设计初衷?
HashSet的核心价值就是基于哈希表的O(1)时间复杂度查找/添加/删除,它的底层完全依赖HashMap的哈希机制。而你重写的contains()方法是遍历整个集合来比较Type,时间复杂度直接降到了O(n),这和用ArrayList遍历判断没区别,完全浪费了HashSet的性能优势,等于把它用成了一个普通的线性集合。
更安全合理的实现方案
既然你的需求是“同一Type的对象仅存一个”,结合你提到的“存储对象均实现同一接口”的场景,推荐两种更靠谱的方案:
方案1:用HashMap封装自定义Set(推荐)
直接基于HashMap实现一个符合你需求的Set,以Type作为key,保证每个Type唯一,同时保留哈希表的O(1)性能:
public class TypeUniqueSet<E extends YourStoredInterface> extends AbstractSet<E> { private final HashMap<Type, E> innerMap = new HashMap<>(); @Override public boolean add(E element) { // putIfAbsent会返回已存在的value,返回null说明是新添加的 return innerMap.putIfAbsent(element.getType(), element) == null; } @Override public boolean contains(Object o) { if (o instanceof YourStoredInterface) { return innerMap.containsKey(((YourStoredInterface) o).getType()); } return false; } @Override public boolean remove(Object o) { if (o instanceof YourStoredInterface) { return innerMap.remove(((YourStoredInterface) o).getType()) != null; } return false; } @Override public Iterator<E> iterator() { return innerMap.values().iterator(); } @Override public int size() { return innerMap.size(); } }
方案2:重写实体类的equals()和hashCode()(如果允许修改实体类)
如果StoredObject(或它的接口实现类)是你可控的,直接重写equals()和hashCode(),用Type作为判断依据:
public class StoredObject implements YourStoredInterface { private Type type; // 其他字段 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || !(o instanceof YourStoredInterface)) return false; YourStoredInterface that = (YourStoredInterface) o; return this.getType() == that.getType(); } @Override public int hashCode() { return Objects.hash(getType()); } }
这样直接用普通的HashSet就能满足需求,因为HashSet会自动遵循你重写的equals/hashCode逻辑,保证同一Type只存一个。
最后总结
- 不要单独重写HashSet的某一个方法,这会破坏集合的行为一致性;
- 避免放弃HashSet的哈希机制,否则就失去了它的性能意义;
- 优先用HashMap封装自定义Set,或者重写实体类的equals/hashCode来契合HashSet的设计逻辑。
内容的提问来源于stack exchange,提问作者Kakoscho

