在Java中HashSet判断重复的依据是什么_Java哈希集合机制解析

胖婷同学_4839

胖婷同学_4839

2026-01-20

341人浏览

原创

HashSet判断重复需同时满足hashCode()相同且equals()返回true;仅用hashCode()会因哈希冲突导致误判;自定义类须重写二者,否则默认基于内存地址比较;底层基于HashMap实现,故支持null、不安全,且去重逻辑等同key去重。

在java中hashset判断重复的依据是什么_java哈希集合机制解析

HashSet 判断重复的依据是:先比 hashCode(),再比 equals() —— 二者必须同时满足,才算重复。

为什么不是只看 hashCode()?

哈希码只是“快速筛人”的第一步。不同对象可能算出相同哈希值(哈希冲突),比如 new String("ABC") 和 new String("BAC") 在某些实现下哈希值可能碰巧一样。如果只依赖 hashCode(),就会误判为重复,导致数据丢失。

  • 只有 hashCode() 相同 → 进入同一哈希桶,触发 equals() 检查
  • hashCode() 不同 → 直接认定不重复,不调用 equals()
  • hashCode() 相同 且 equals() 返回 true → 确认为重复,add() 返回 false,不插入

自定义类没重写 hashCode() 和 equals() 会怎样?

默认继承自 Object,hashCode() 返回对象内存地址哈希,equals() 是引用比较。结果就是:两个字段完全相同的 Student 实例,在 HashSet 里仍被视为不同元素。

常见错误现象:

  • 明明构造了两个 new Student(1, "Alice"),set.size() 却是 2
  • set.contains(new Student(1, "Alice")) 返回 false,即使集合里已有逻辑相同的对象

正确做法是:用 Objects.hash(...) 生成 hashCode(),用 Objects.equals(...) 辅助写 equals():

public class Student {
    private int id;
    private String name;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Student)) return false;
        Student s = (Student) o;
        return id == s.id && Objects.equals(name, s.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name);
    }
}

HashSet 底层其实是 HashMap,这有什么影响?

是的 —— HashSet 的每个元素都作为 HashMap 的 key 存储,value 固定是 Presentation(一个静态 Object 占位符)。这意味着:

  • 所有去重逻辑,本质就是 HashMap.put(key, value) 的 key 去重逻辑
  • null 元素能存,因为 HashMap 允许一个 null key
  • 线程不安全:多线程往同一个 HashSet add() 可能导致数据错乱或死循环(JDK 7 及以前链表成环)

如果你需要线程安全的去重容器,别手动加 synchronized,优先考虑 Collections.synchronizedSet(new HashSet()) 或 ConcurrentHashMap.newKeySet()(JDK 8+)。

最容易被忽略的一点:重写 hashCode() 和 equals() 时,参与计算的字段必须是「业务上决定唯一性」的字段。比如学生去重按学号,就别把 name 也塞进 Objects.hash() —— 否则改名后,旧对象在集合里就再也 contains() 不到了。

Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南

相关专题

更多
string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.02

6759

3

c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

2023.09.22

549

3

java中null的用法
java中null的用法

在Java中,null表示一个引用类型的变量不指向任何对象。可以将null赋值给任何引用类型的变量,包括类、接口、数组、字符串等。想了解更多null的相关内容,可以阅读本专题下面的文章。

2024.03.01

1698

6

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

2023.08.10

4438

6

Python 多线程与异步编程实战
Python 多线程与异步编程实战

本专题系统讲解 Python 多线程与异步编程的核心概念与实战技巧,包括 threading 模块基础、线程同步机制、GIL 原理、asyncio 异步任务管理、协程与事件循环、任务调度与异常处理。通过实战示例,帮助学习者掌握 如何构建高性能、多任务并发的 Python 应用。

2025.12.24

1229

20

java多线程相关教程合集
java多线程相关教程合集

本专题整合了java多线程相关教程,阅读专题下面的文章了解更多详细内容。

2026.01.21

423

30

C++多线程相关合集
C++多线程相关合集

本专题整合了C++多线程相关教程,阅读专题下面的的文章了解更多详细内容。

2026.01.21

579

24

C# 多线程与异步编程
C# 多线程与异步编程

本专题深入讲解 C# 中多线程与异步编程的核心概念与实战技巧,包括线程池管理、Task 类的使用、async/await 异步编程模式、并发控制与线程同步、死锁与竞态条件的解决方案。通过实际项目,帮助开发者掌握 如何在 C# 中构建高并发、低延迟的异步系统,提升应用性能和响应速度。

2026.02.06

429

20

C++多线程并发控制与线程安全设计实践
C++多线程并发控制与线程安全设计实践

本专题围绕 C++ 在高性能系统开发中的并发控制技术展开,系统讲解多线程编程模型与线程安全设计方法。内容包括互斥锁、读写锁、条件变量、原子操作以及线程池实现机制,同时结合实际案例分析并发竞争、死锁避免与性能优化策略。通过实践讲解,帮助开发者掌握构建稳定高效并发系统的关键技术。

2026.03.16

253

22

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
dev.java 官方:Learn Java
dev.java 官方:Learn Java

共0课时 | 0人学习

Java JDBC数据库连接官方教程
Java JDBC数据库连接官方教程

共0课时 | 0人学习