HashSet去重只依赖equals()和hashCode()实现,与是否深拷贝无关;深拷贝解决对象隔离,去重解决逻辑唯一性,二者需通过正确重写equals/hashCode协同工作。

HashSet 本身不处理深拷贝,它只负责去重;去重是否生效,取决于对象的 hashCode() 和 equals() 实现,而不是对象是否被深拷贝。
HashSet 去重和深拷贝是两件事
深拷贝是指创建一个新对象,其内部所有引用类型字段也都被复制为独立副本;而 HashSet 的“去重”判断完全基于逻辑相等性——即两个对象内容相同、应视为同一个元素时,只保留一个。
关键点在于:
- HashSet 不关心对象是不是深拷贝来的,只调用它的
hashCode()和equals() - 如果你把一个对象深拷贝后加入 HashSet,只要拷贝体与原对象满足
equals() == true且hashCode()相同,它们就会被当作重复项,后者加不进去 - 反之,如果没重写这两个方法,哪怕内容一模一样,也会被当成两个不同对象存进去
想让深拷贝后的对象参与正确去重,必须重写 equals 和 hashCode
比如你有一个 User 类,并用工具(如 Apache Commons Lang 的 SerializationUtils.clone())做了深拷贝:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
立即学习“Java免费学习笔记(深入)”;
- 深拷贝产生的是新对象(内存地址不同),但内容相同
- 若未重写
equals(),默认用==比较,结果为false - 若未重写
hashCode(),默认返回内存地址哈希,两个对象哈希值不同 → HashSet 放进不同桶 → 不触发equals()比较 → 全部保留 - 所以必须同时重写二者,且保证:内容相同 →
hashCode()相同 →equals()返回true
实际操作建议
不需要在 HashSet 层面做任何特殊配置。只需确保你的对象类满足以下条件:
- 所有参与逻辑相等判断的字段,都用于生成
hashCode()和参与equals()比较 - 推荐用 IDE 自动生成(如 IntelliJ 的
Alt+Insert),或使用Objects.hash(...)和Objects.equals(...) - 如果对象含嵌套引用(如
Address address),要确保Address类也正确实现了equals/hashCode - 避免在
hashCode()或equals()中使用可变字段(如未设为final的属性),否则对象加入 HashSet 后修改字段,可能导致无法contains()或remove()
小结:别混淆概念
深拷贝解决的是对象隔离问题(避免修改影响原对象);HashSet 去重解决的是逻辑唯一性问题。两者目标不同,但可以共存——前提是你的对象契约写对了。只要 equals 和 hashCode 正确,不管对象是 new 出来的、序列化克隆的,还是 JSON 反序列化的,HashSet 都能按你期望的方式去重。

















