Java学习21
·
上午 3h Set 集合总览 + HashSet 基础
1.1 Set 接口核心特征(0.5h 必背)
核心三大特点(和 List 完全反着记)
- 无序:存取顺序不一致,存的顺序和取出顺序不保证相同
- 不可重复:自带自动去重,不允许存储重复元素
- 无索引:没有数字下标,不能使用普通 for 循环遍历
补充基础
- Set 同样继承
Collection顶层接口 - 通用方法和 List 完全一致:
add()、remove()、contains()、size()、clear() - 只是特性、底层结构、去重规则不一样
极简对比记忆
- List:有序、可重复、有索引
- Set:无序、不可重复、无索引
1.2 Set 三大常用实现类(0.3h)
- HashSet底层:哈希表(数组 + 链表 + 红黑树)特点:无序、去重、查询存取速度极快,开发最常用
- LinkedHashSet底层:哈希表 + 双向链表特点:存取有序 + 自动去重
- TreeSet底层:红黑树特点:自动自然排序 + 去重
1.3 HashSet 基础使用(1h)
1. 创建对象 + 泛型语法
java
运行
// 泛型约束:只能存储字符串
HashSet<String> set = new HashSet<>();
2. 通用常用 API
完全复用 Collection 通用方法:
add(E e):添加元素remove(Object o):删除指定元素contains(Object o):判断是否包含size():获取元素个数clear():清空集合
3. Set 仅支持的两种遍历方式
因为无索引,直接淘汰普通 for:
- 增强 for 循环(推荐)
- 迭代器 Iterator 遍历
完整基础演示代码 + 逐行解析
java
运行
import java.util.HashSet;
import java.util.Iterator;
public class HashSetBaseDemo {
public static void main(String[] args) {
// 1. 创建HashSet集合,泛型约束String
HashSet<String> set = new HashSet<>();
// 2. 添加元素
set.add("Java");
set.add("C++");
set.add("Python");
// 3. 通用方法测试
System.out.println("元素个数:" + set.size());
System.out.println("是否包含Java:" + set.contains("Java"));
// 4. 增强for遍历(无索引首选)
System.out.println("===== 增强for遍历 =====");
for (String s : set) {
System.out.println(s);
}
// 5. 迭代器遍历
System.out.println("===== 迭代器遍历 =====");
Iterator<String> it = set.iterator();
while (it.hasNext()) {
String s = it.next();
System.out.println(s);
}
}
}
逐行解释
HashSet<String> set = new HashSet<>();创建哈希表集合,强制只能存字符串,编译类型校验;- 所有 add 添加的元素,底层会自动校验重复;
- 无索引 → 不能用
get(索引)、不能用普通 for; - 迭代器、增强 for 是 Set 标准遍历方案。
1.4 字符串自动去重实操(1.2h)
核心原理
String 类 官方已经重写好了 hashCode() 和 equals()所以 HashSet 可以直接对字符串自动去重。
去重 + 无序完整案例代码
java
运行
import java.util.HashSet;
public class SetStringRepeatDemo {
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
// 故意添加大量重复元素
set.add("张三");
set.add("李四");
set.add("张三");
set.add("王五");
set.add("李四");
// 直接打印:无序 + 自动去重
System.out.println(set);
}
}
运行现象
- 输出顺序和添加顺序不一样 → 验证无序
- 重复的「张三、李四」只保留一份 → 验证不可重复
关键结论
- String、Integer 等 JDK 自带类,都重写了哈希与比较方法
- 存入 HashSet 天然支持去重,直接即用
下午 2.5h 哈希底层 + 去重核心原理(面试高频)
2.1 哈希值 hashCode(0.7h)
1. 概念
哈希值:JDK 根据对象,通过算法算出的int 类型整数编号相当于:对象的「身份证编号」
2. 核心方法
java
运行
// Object类自带方法,所有对象都有
public int hashCode()
3. 三大特点
- 同一个对象,多次调用
hashCode(),哈希值固定不变 - 不同对象,哈希值绝大多数不同
- 存在哈希冲突:不同对象,刚好算出相同哈希值(小概率)
简单代码演示
java
运行
public class HashCodeDemo {
public static void main(String[] args) {
String s1 = "abc";
String s2 = "abc";
String s3 = "def";
System.out.println(s1.hashCode());
System.out.println(s2.hashCode()); // 相同内容,哈希值一致
System.out.println(s3.hashCode()); // 不同内容,哈希值不同
}
}
2.2 HashSet 底层结构(JDK1.8+ 0.9h)
底层组合
哈希表 = 数组 + 单向链表 + 红黑树
元素存入完整流程
- 调用对象
hashCode()计算哈希值 - 哈希值经过算法,换算成数组下标
- 判断数组下标位置是否为空
- 为空:直接存入该位置
- 不为空:产生哈希冲突,向下挂载链表
- 链表长度 ≥ 阈值,自动转为红黑树,提升查询效率
结构优势
- 数组:寻址快
- 链表:解决哈希冲突
- 红黑树:防止链表过长导致效率过低
2.3 HashSet 去重双重规则(重中之重 0.9h)
两个条件 必须同时满足,才判定为重复元素、自动去重
- 两个对象的 hashCode () 哈希值相同
- 两个对象调用
equals()比较,返回 true
完整判断流程
- 先对比哈希值
- 哈希值不同 → 直接判定为不同元素,直接存入
- 哈希值相同 → 进入第二步
- 再调用 equals 比较内容
- equals 为 true → 判定重复,舍弃不存入
- equals 为 false → 哈希冲突,挂载链表存储
面试必背一句话
先比哈希码,再比内容;哈希不同直接存,哈希相同比 equals。
晚上 1.5h 自定义对象去重 + 复盘实战
3.1 自定义对象无法去重的原因(0.8h)
现象
自己写的 Student 类,name、age 完全一样;存入 HashSet 不会自动去重。
根本原因
- 自定义类默认继承 Object
- 使用 Object 原生的:
hashCode():比较内存地址equals():比较内存地址
- new 出来的两个对象,地址一定不同 → 永远判定不重复
唯一解决方案
必须手动重写两个方法
- 重写
equals():对比 对象内部属性(姓名、年龄) - 重写
hashCode():根据属性计算哈希值
实操统一使用 IDEA 一键生成:Alt+Insert → equals and hashCode
3.2 全套实战练习(完整代码 + 逐行解析)
步骤 1:未重写方法 —— 无法去重
java
运行
// 学生类:只写成员变量、构造、get/set、toString
public class Student {
private String name;
private int age;
public Student() {}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// get & set 省略
public String getName() { return name; }
public int getAge() { return age; }
@Override
public String toString() {
return "Student{name='" + name + "', age=" + age + "}";
}
// 关键:没有重写 equals()、hashCode()
}
测试类:
java
运行
import java.util.HashSet;
public class StudentSetNoRewrite {
public static void main(String[] args) {
HashSet<Student> set = new HashSet<>();
// 内容完全相同,但是两个不同对象
Student s1 = new Student("张三", 18);
Student s2 = new Student("张三", 18);
set.add(s1);
set.add(s2);
// 输出两个对象,无法去重
System.out.println(set);
}
}
步骤 2:IDEA 自动重写后 —— 成功去重
在 Student 类空白处 Alt+Insert → 选择 equals() and hashCode(),自动生成:
java
运行
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
再次运行上面测试代码:
- 重复属性的学生自动去重
- 只保留一个对象,完全符合业务需求
3.3 今日全量复盘总结
- Set 集合三大核心:无序、无索引、不可重复
- HashSet 底层:哈希表(数组 + 链表 + 红黑树)
- 哈希值:对象唯一整数标识,Object 原生提供
- 去重核心逻辑:hashCode 一致 + equals 为 true 双重校验
- JDK 自带类(String/Integer)已重写方法,天然去重
- 自定义实体类:必须重写 equals+hashCode 才能实现去重
Day21 验收标准(自查)
✅ 熟练口述 Set 与 List 区别、三大特点✅ 独立写出 HashSet 增删查、增强 for / 迭代器两种遍历✅ 理解哈希值概念、哈希表底层组成✅ 完整默写 HashSet 双重去重判断流程✅ 会使用 IDEA 一键生成 equals&hashCode,解决自定义对象去重
总结一下SET的无序
超级大白话总结(必背)
- HashSet 不是随机乱序!
- 每次运行打印顺序都一样,永远不变!
- 无序 = 存的顺序 ≠ 取的顺序
- 顺序由 哈希值 决定,固定不变
- 你添加每一个元素,会固定算出唯一哈希值
- 哈希值 → 算出固定数组下标(存放位置)
- 存放位置永远不变
- 所以每次运行程序,遍历 / 打印顺序完全一模一样
补充:我们还可以尝试将今天的代码全写在一个类里,要用到我们之前学习的知识类不能定义在方法内部且只能有一个public class
更多推荐

所有评论(0)