【Java】HashSet存入学生对象

本文介绍了如何使用Java的HashSet存储Student对象,并通过重写equals()和hashCode()方法确保集合中无重复项。同时提及了HashSet底层原理和HashMap的Get()方法的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

使用 HashSet 存入学生对象

  1. 定义学生 Student 实体类 成员属性 学号、姓名
  2. 定义 HashSet 集合存入 Student 对象,保证存放的学生对象(相同的学号和姓名)不允许重复
  3. 定义 StudentManage 对 Student 对象集合实现增删改查

示例代码

package com.collection.Demo09;

import java.util.Objects;

public class Student {
    private String id;//学号
    private String name;//姓名

    public Student(String id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                '}';
    }
    //重写equals()实现 比较两个对象的属性值 如果是一样的 则返回true
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;//比较两个对象内存地址
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return Objects.equals(id, student.id) && Objects.equals(name, student.name);
    }

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

import java.util.HashSet;

public class Test04 {
    public static void main(String[] args) {
        /**
         * HashSet 底层是基于HashMap集合实现的
         * HashMap 底层是不允许Key重复
         * HashMap 底层add()是如何实现新增元素 保证key 不允许重复的呢?
         * if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
         * 第一次 add s1对象 存入在HashMap集合
         * 第二次调用add()时,存入s2对象 s1==s2(false)||s1.equals(s2)(false)(∵没有重写equals(),底层还是在判断内存地址是否相同)
         * s1==s4 相同的内存地址 不能够继续存入 hashMap集合中
         */
        HashSet<Student> students = new HashSet<>();
        Student s1 = new Student("001", "mayikt01");
        Student s2 = new Student("001", "mayikt01");
        Student s3 = new Student("001", "mayikt01");
        Student s4 = s1;
        Student s5 = new Student("002", "mayikt01");
        students.add(s1);
        students.add(s2);
        students.add(s3);
        students.add(s4);//s1==s4 相同的内存地址 不能够继续存入 hashMap集合中
        students.add(s5);
        System.out.println(students.size());//2
        for (Student stu: students) {
            System.out.println(stu);//Student{id='001', name='mayikt01'}
                                    //Student{id='002', name='mayikt01'}

        }
        /**
         * HashSet去除重复数据
         * 需要重写元素对应的equals()和hashCode(),idea可以自动生成,比较两个对象成员属性值是否是一样的
         */
    }
}

下一篇文章:HashMap集合常用Get()

<think>我们参考了多个引用,特别是引用[1]、[2]、[3]和[4],它们都提到了在Java中使用Set对自定义对象去重的关键点:必须正确重写`hashCode()`和`equals()`方法。这是因为Set集合(如HashSet)依赖这两个方法来判断对象的唯一性。 ### 一、自定义对象实现Set去重的原理 1. **底层机制**: - 当向Set中添加一个对象时,会先计算该对象的`hashCode()`值,根据该值确定存储位置(桶)。 - 如果该位置没有对象,则直接存入。 - 如果该位置已有对象(哈希冲突),则调用`equals()`方法比较两个对象是否相等: - 若相等,视为重复,不添加; - 若不相等,则以链表或红黑树形式存储(不同JDK版本实现不同)[^1][^3]。 2. **默认行为的问题**: - 未重写`hashCode()`和`equals()`时,Java使用`Object`的默认实现: ```java public int hashCode() { return System.identityHashCode(this); // 基于内存地址 } public boolean equals(Object obj) { return (this == obj); // 比较对象地址 } ``` - 导致内容相同的对象因地址不同而被视为不同对象,无法去重[^1]。 ### 二、正确去重的步骤(以学生为例) #### 1. 定义实体并重写关键方法 ```java import java.util.Objects; public class Student { private int id; private String name; // 构造方法、Getter/Setter省略 @Override public boolean equals(Object o) { // 1. 地址相同直接返回true if (this == o) return true; // 2. 非空且类型相同才比较 if (o == null || getClass() != o.getClass()) return false; Student student = (Student) o; // 3. 关键:比较业务主键(此处以id为唯一标识) return id == student.id; } @Override public int hashCode() { // 使用相同字段生成哈希值(与equals()保持一致) return Objects.hash(id); } } ``` #### 2. 使用Set去重 ```java Set<Student> studentSet = new HashSet<>(); studentSet.add(new Student(1, "张三")); studentSet.add(new Student(1, "张三")); // 因id相同,被判定为重复 System.out.println(studentSet.size()); // 输出1 ``` ### 三、特殊情况处理 #### 1. 多字段联合去重 若需多个字段共同决定唯一性(如id+name): ```java @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Student student = (Student) o; // 联合判断id和name return id == student.id && Objects.equals(name, student.name); } @Override public int hashCode() { // 联合生成哈希值 return Objects.hash(id, name); } ``` #### 2. 使用Stream API去重(无需额外重写) 若无法修改的源码(如使用第三方库),可用Stream的`distinct()`配合自定义比较器: ```java List<Student> students = Arrays.asList( new Student(1, "张三"), new Student(1, "张三") ); List<Student> uniqueList = students.stream() .distinct() // 依赖equals和hashCode,若未重写则无效 .collect(Collectors.toList()); ``` **替代方案**(使用`TreeSet`或自定义比较器): ```java Set<Student> uniqueSet = students.stream() .collect(Collectors.toCollection( () -> new TreeSet<>(Comparator.comparingInt(Student::getId)) )); // 注意:TreeSet根据Comparator判断重复(非equals/hashCode) ``` ### 四、验证去重是否生效 ```java Student s1 = new Student(1, "张三"); Student s2 = new Student(1, "张三"); System.out.println(s1.equals(s2)); // 应输出true System.out.println(s1.hashCode() == s2.hashCode()); // 应输出true ``` ### 五、常见错误及规避 1. **哈希值生成不一致**: - 错误示例:重写`hashCode()`时漏掉字段 ```java // 错误!只用了id没包含name return Objects.hash(id); ``` - 规避:确保`hashCode()`和`equals()`使用相同字段集合。 2. **修改参与哈希计算的字段**: - 若将已存入Set的对象修改其关键字段: ```java Student s = new Student(1, "张三"); set.add(s); s.setId(2); // 修改后导致哈希值变化! ``` - 结果:Set无法正确定位该对象,造成内存泄漏。 - 规避:**设计不可变对象**(用`final`修饰关键字段)。 3. **继承破坏约定**: - 若`Student`有子`CollegeStudent`,且重写了`equals()`: ```java // 在子中 @Override public boolean equals(Object o) { if (!(o instanceof CollegeStudent)) return false; // ... } ``` - 导致`student.equals(collegeStudent)`可能不对称。 - 规避:**用`getClass()`代替`instanceof`**(如上例),或遵循里氏替换原则重新设计。 > 遵循以上步骤,即可确保自定义对象在Set中正确去重。核心原则:**重写`equals()`和`hashCode()`,保持逻辑一致,且对象关键字段不可变**。[^1][^2][^3][^4] §§相关问题§§ 1. 为什么重写`equals()`时必须同时重写`hashCode()`方法? 2. 如何设计不可变对象以保证Set中元素的安全性? 3. `TreeSet`和`HashSet`在去重机制上有何本质区别? 4. 使用`Objects.hash()`生成哈希值是否存在碰撞风险?如何优化?</think>### Java 自定义对象实现 Set 正确去重的方法 在 Java 中,要使自定义对象在 `Set` 中正确去重,**必须重写 `hashCode()` 和 `equals()` 方法**。这是 Set 判断对象唯一性的底层机制(引用[1][3])。以下是完整实现步骤: --- #### 一、核心原理 1. **去重逻辑**: - 添加对象时,先调用 `hashCode()` 计算哈希值 - 若哈希值不存在,直接存入 - 若哈希值存在,再调用 `equals()` 比较对象内容 - 只有哈希值相同 **且** `equals()` 返回 `true` 才视为重复(引用[1][3]) 2. **默认行为问题**: ```java // 未重写方法时(使用Object默认实现) Student s1 = new Student(1, "张三"); Student s2 = new Student(1, "张三"); set.add(s1); set.add(s2); // 会被当作不同对象存入! ``` 原因:默认 `hashCode()` 基于内存地址,`equals()` 比较对象引用(引用[1]) --- #### 二、正确实现步骤(以 Student 为例) ```java import java.util.Objects; public class Student { private final int id; // 关键字段建议用final private final String name; public Student(int id, String name) { this.id = id; this.name = name; } // 步骤1:重写equals() - 定义业务唯一性 @Override public boolean equals(Object o) { // 1. 地址相同直接返回true if (this == o) return true; // 2. 类型不同直接返回false if (o == null || getClass() != o.getClass()) return false; // 3. 比较关键字段值 Student student = (Student) o; return id == student.id; // 根据id判断是否同一学生 } // 步骤2:重写hashCode() - 与equals()逻辑一致 @Override public int hashCode() { // 使用相同字段生成哈希值 return Objects.hash(id); } } ``` --- #### 三、使用示例 ```java Set<Student> studentSet = new HashSet<>(); studentSet.add(new Student(101, "张三")); studentSet.add(new Student(101, "张三")); // 相同id,被去重 studentSet.add(new Student(102, "李四")); System.out.println(studentSet.size()); // 输出2 ``` --- #### 四、多字段联合去重 若需多个字段共同标识唯一性(如 id + name): ```java @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Student student = (Student) o; // 联合判断id和name return id == student.id && Objects.equals(name, student.name); } @Override public int hashCode() { // 联合生成哈希值 return Objects.hash(id, name); } ``` --- #### 五、无法修改源码时的替代方案 1. **使用 TreeSet + Comparator**: ```java Set<Student> uniqueSet = new TreeSet<>( Comparator.comparingInt(Student::getId) .thenComparing(Student::getName) ); ``` > 📝 注意:`TreeSet` 用 `Comparator`/`Comparable` 判断重复,而非 `equals()`(引用[3]) 2. **Stream API 去重**: ```java List<Student> distinctList = students.stream() .distinct() // 依赖equals/hashCode .collect(Collectors.toList()); ``` 或使用自定义比较器: ```java List<Student> distinctById = students.stream() .collect(Collectors.collectingAndThen( Collectors.toCollection( () -> new TreeSet<>(Comparator.comparingInt(Student::getId)) ), ArrayList::new )); ``` --- #### 六、关键注意事项 1. **一致性要求**: - `a.equals(b) == true` 时,`a.hashCode() == b.hashCode()` **必须成立** - 反之不一定成立(哈希碰撞允许) 2. **对象不可变性**: ```java Student s = new Student(101, "张三"); set.add(s); s.setId(999); // 危险!修改后会导致Set内部状态错乱 ``` **解决方案**: - 将关键字段声明为 `final` - 避免在对象存入 Set 后修改关键字段 3. **工具推荐**: - 使用 IDE 自动生成 `equals()` 和 `hashCode()`(如 IntelliJ 的 `Alt+Insert`) - 使用 `Objects.hash()` 简化哈希值计算(Java 7+) > 通过正确实现 `hashCode()` 和 `equals()`,可确保自定义对象在 `HashSet`, `LinkedHashSet` 等实现中正确去重(引用[1][2][3][4])。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值