c260803course / homework0731之前学过的 HashMap / HashSet 是无序集合——取出的顺序和存入的顺序不一致(底层根据 hashCode 计算存储位置)。
而 TreeMap / TreeSet 是有序集合——取出的顺序和存入的顺序一致(注意:其实也不一定完全一致,而是排序过的)。
⚠️ 不是所有类型(包括自定义类)都能直接存入 TreeSet 或 TreeMap,是有条件的:
- 该类型实现了
Comparable接口并重写了compareTo(T o)方法(自然排序);- 或者在创建集合对象时,在构造方法中传入比较器对象
Comparator,进行临时的自定义排序(比较器排序)。
| 排序方式 | 实现方式 | 使用场景 |
|---|---|---|
| 自然排序(Comparable) | 实体类实现 Comparable 接口,重写 compareTo(T o) |
默认排序方式,类设计期间就确定好的排序规则 |
| 比较器排序(Comparator) | 创建集合对象时传入 Comparator 接口实现类,重写 compare() |
类定义期间没实现 Comparable,或临时需要其他方式排序 |
使用原则:一般还是以实现 Comparable 接口为主;只有在类定义期间没有实现 Comparable 接口,或者加入集合时需要临时做其他方式的排序,才会使用 Comparator 进行临时自定义排序。
// 来源:course/Demo01.java(注释部分)
public class 类名 implements Comparable<类名>{
private 类型 属性1;
private 类型 属性2;
...
private 类型 属性n;
无参构造方法
有参构造方法(参数...){ }
// 可选:内部类、代码块...
getter方法...
setter方法...
equals()方法...
hashCode()方法...
toString()方法...
compareTo(T o)方法...
}
自然排序:实体类实现 Comparable 接口,重写 compareTo(T o) 方法,根据返回值决定排序规则。这是类的默认排序方式。
// 来源:course/Student.java
package course;
public class Student implements Comparable<Student>{
private String name;
private int age;
private double score;
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
// getter / setter ...(略)
// toString() ...(略)
@Override
public int compareTo(Student o) {
// 设置按照年龄排序(从小到大)
int result = this.age - o.getAge();
// 年龄相同 → 再按姓名排序(二次比较,保证唯一性)
return result == 0 ? this.name.compareTo(o.getName()) : result;
}
}
this.age - o.getAge() 正负决定了当前对象比传入对象大还是小。比较器排序:创建集合对象的时候,传入 Comparator 接口的实现类对象,重写 compare() 方法,根据返回值进行排序。不会修改实体类本身,是临时的、自定义的排序规则。
通常配合匿名内部类使用,实现 Comparator<Student> 并重写 compare(Student o1, Student o2)。
// 来源:course/Demo01.java(部分)
package course;
import java.util.Comparator;
import java.util.TreeSet;
public class Demo01 {
public static void main(String[] args) {
// 按照自定义的要求临时进行排序:先按成绩升序,成绩相同再按姓名排序
TreeSet<Student> set = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
int result = (int) (o1.getScore() - o2.getScore());
return result == 0 ? o1.getName().compareTo(o2.getName()) : result;
}
});
set.add(new Student("张三", 7, 85));
set.add(new Student("李四", 2, 90));
set.add(new Student("王五", 20, 78));
set.add(new Student("赵六", 222, 88));
for (Student s : set) {
System.out.println(s);
}
}
}
// 来源:course/Demo01.java(部分)
// 不传 Comparator → 使用 Student 类自带的自然排序(Comparable:按年龄)
TreeSet<Student> treeSet = new TreeSet<>(); // 默认使用自然排序
treeSet.add(new Student("张三", 20, 85));
treeSet.add(new Student("李四", 22, 90));
treeSet.add(new Student("王五", 20, 78)); // 年龄 20 与"张三"相同 → 触发 name 二次比较
treeSet.add(new Student("赵六", 21, 88));
for (Student s : treeSet) {
System.out.println(s);
}
new TreeSet<>(comparator) 用的是比较器排序;new TreeSet<>() 用的是自然排序。| 返回值 | 含义 | 处理结果 |
|---|---|---|
| 负数 | 当前存入的元素比较小 | 存左边 |
| 0 | 当前存入的元素和已有元素「重复」 | 不存(TreeSet 去重 / TreeMap 键去重) |
| 正数 | 当前存入的元素比较大 | 存右边 |
因为这样能更好地存入 TreeMap / TreeSet 集合:
x1.compareTo(x2) == 0 或 compare(x1, x2) == 0,那么建议 x1.equals(x2) 返回 true,并且 hashCode 一致。这样排序判定、相等判定、哈希判定三者保持一致,集合的「去重」行为才符合预期。
创建
HashMap<String, Integer>保存学号-成绩,练习put/size/get/containsKey/remove等基本操作;分别用keySet + get和entrySet两种方式遍历;再创建TreeMap观察按键自动排序的效果。
// 来源:homework0731/p2_map/MapDemo.java
package homework0731.p2_map;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
public class MapDemo {
public static void main(String[] args) {
// 1. 创建 HashMap,添加 4 组成绩
HashMap<String, Integer> hashMap = new HashMap<>();
hashMap.put("x001", 90);
hashMap.put("x002", 85);
hashMap.put("x003", 92);
hashMap.put("x004", 78);
// 2. 输出集合大小
int size = hashMap.size(); // 学生人数:4
// 3. 查询 / 判断键是否存在
// get("x002") → 85
// containsKey("x001") → true,containsKey("x999") → false
// 4. 修改:put("x004", 88)(键已存在 → 覆盖旧值)
// 5. 删除:remove("x001")
// 6. 遍历方式二:entrySet() + getKey()/getValue()
Set<Map.Entry<String, Integer>> entries = hashMap.entrySet();
for (Map.Entry<String, Integer> entry : entries) {
System.out.println(entry.getKey() + "--->" + entry.getValue());
}
// 7. TreeMap:放入同样的 4 组数据,按键(学号)自动排序
TreeMap<String, Integer> treeMap = new TreeMap<>();
treeMap.put("x001", 90);
treeMap.put("x002", 85);
treeMap.put("x003", 92);
treeMap.put("x004", 78);
Set<String> set = treeMap.keySet();
for (String key : set) {
Integer value = treeMap.get(key);
System.out.println(key + "--->" + value);
}
}
}
| 知识点 | 说明 |
|---|---|
put(key, value) |
添加键值对;键已存在时覆盖旧值(即修改) |
size() / get(key) |
集合大小 / 根据键取值 |
containsKey(key) |
判断键是否存在 |
remove(key) |
根据键删除键值对 |
| keySet() 遍历 | 先取所有键(Set<K>),再 get(key) 反查值 |
| entrySet() 遍历 | 一次性拿到键值对 Entry,getKey()/getValue() 直接取值,效率更高 |
| TreeMap | 按键自动排序(学号 x001→x004 按字典序输出),与 HashMap 无序形成对比 |
循环输入 5 个成绩(0~100),用 try-catch 处理异常:输入非整数 →
InputMismatchException;成绩不在 0~100 → 抛出自定义InvalidScoreException;最后用迭代器遍历输出成绩。
// 来源:homework0731/p4_exception/InvalidScoreException.java
package homework0731.p4_exception;
// 自定义运行时异常:继承 RuntimeException,构造方法传入错误信息
public class InvalidScoreException extends RuntimeException {
public InvalidScoreException(String message) {
super(message);
}
}
// 来源:homework0731/p4_exception/ScoreStatisticsDemo.java
package homework0731.p4_exception;
import java.util.ArrayList;
import java.util.InputMismatchException;
import java.util.Iterator;
import java.util.Scanner;
public class ScoreStatisticsDemo {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
ArrayList<Integer> scores = new ArrayList<>();
// 循环输入 5 个合法成绩;i 只有输入成功才自增(保证最终拿到 5 个合法成绩)
for (int i = 0; i < 5; ) {
try {
int s = sc.nextInt();
if (s < 0 || s > 100) {
throw new InvalidScoreException("成绩必须在0~100之间"); // 主动抛出自定义异常
}
scores.add(s);
i++;
} catch (InputMismatchException e) { // 输入的不是整数
System.out.println("请输入整数");
sc.nextLine(); // 清空缓冲区,避免死循环
} catch (InvalidScoreException e) { // 成绩越界
System.out.println("成绩不合法:成绩必须在0~100之间");
}
}
// 使用迭代器遍历输出所有成绩
Iterator<Integer> iterator = scores.iterator();
while (iterator.hasNext()) {
Integer i = iterator.next();
System.out.print(i + ", ");
}
}
}
| 知识点 | 说明 |
|---|---|
| 自定义异常 | 继承 RuntimeException(运行时异常)即可,通过构造方法传入 message |
| throw 主动抛出 | throw new InvalidScoreException("...") 在业务逻辑中主动制造异常 |
| 多 catch 块 | 一个 try 可配多个 catch,分别捕获 InputMismatchException 和自定义异常 |
sc.nextLine() 清空缓冲 |
捕获 InputMismatchException 后必须清空输入缓冲,否则会陷入死循环 |
| 循环内 i++ 放在 try 内 | 只有输入合法才自增计数,保证最终得到 5 个合法成绩(程序更鲁棒) |
创建
ArrayList<Student>,用匿名内部类实现Comparator<Student>分别按年龄升序、成绩降序排序(Collections.sort);再创建TreeSet<Student>传入按姓名排序的匿名 Comparator,观察自动排序。
// 来源:homework0731/p5_sort/SortDemo.java
package homework0731.p5_sort;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class SortDemo {
public static void main(String[] args) {
ArrayList<Student> list = new ArrayList<>();
list.add(new Student("张三", 20, 85));
list.add(new Student("李四", 22, 90));
list.add(new Student("王五", 19, 78));
list.add(new Student("赵六", 21, 88));
// 1. 匿名内部类实现 Comparator,按年龄升序排序
Collections.sort(list, new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
return s1.getAge() - s2.getAge(); // 年龄升序
}
});
for (Student s : list) {
System.out.println(s);
}
// 2. 匿名内部类 Comparator,按成绩(double)降序排序
// 注意:double 不能直接相减强转,用 Double.compare 最安全
Collections.sort(list, new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
return Double.compare(s2.getScore(), s1.getScore()); // 成绩降序
}
});
// 3. 创建 TreeSet<Student>,传入按姓名排序的匿名 Comparator,迭代器遍历观察自动排序
// TreeSet<Student> treeSet = new TreeSet<>(comparator); // 添加学生后自动按姓名排序
}
}
// 来源:homework0731/p5_sort/Student.java(普通实体类,未实现 Comparable)
package homework0731.p5_sort;
public class Student {
private String name;
private int age;
private double score;
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
// getter / setter ...(略)
// toString() ...(略)
}
| 知识点 | 说明 |
|---|---|
Collections.sort(list, comparator) |
对 List 集合用比较器排序(原地排序,改变原集合) |
| 匿名内部类 Comparator | 临时指定排序规则,无需修改 Student 类(本例 Student 未实现 Comparable) |
Double.compare(a, b) |
double 类型比较的正确姿势:浮点数相减精度丢失,且无法强转 int;用 Double.compare 最安全 |
| Comparator 降序技巧 | 交换 compare 中两个参数的位置即可反向:compare(s2, s1) = 降序 |
| TreeSet + Comparator | 无参构造用自然排序;传入 Comparator 的构造用比较器排序,存入即自动排序 |
核心步骤:
Student 类实现 Comparable<Student>,compareTo 按年龄升序(年龄相同再按姓名)new TreeSet<>() 存入 4 名学生 → 观察自然排序结果new TreeSet<>(new Comparator<Student>() {...}) 按成绩升序 → 观察比较器排序结果思考:
compareTo 返回 0 视为重复,不存入;所以排序依据最好唯一,或叠加二次比较(姓名)核心步骤:
HashMap 的 put/size/get/containsKey/remove 五类操作keySet() + get() 遍历;entrySet() + getKey()/getValue() 遍历TreeMap 用同样数据验证按键自动排序思考:
put 键已存在时是「添加」还是「修改」?—— 覆盖旧值,等价于修改get(key) 反查一次核心步骤:
InvalidScoreException extends RuntimeExceptionInputMismatchException / InvalidScoreException)思考:
InputMismatchException 后为什么要 sc.nextLine()?—— 清空输入缓冲区,否则错误输入会反复触发异常形成死循环RuntimeException 和 Exception 有何区别?—— RuntimeException 是运行时异常,方法内抛出可不用显式声明 throws,编译不强制处理核心步骤:
Collections.sort(list, new Comparator<Student>(){...}) 按年龄升序Double.compare(s2.getScore(), s1.getScore())TreeSet<Student> 传入按姓名排序的匿名 Comparator,迭代器遍历思考:
(int)(s1.getScore()-s2.getScore())?—— double 相减可能有精度误差,强转 int 也不安全;Double.compare 是标准做法| 前置知识 | 当前知识 | 后续知识 |
|---|---|---|
| 匿名内部类(0727) | Comparator 匿名内部类实现 | Lambda 表达式(函数式接口) |
| 集合体系与泛型(0729) | TreeSet / TreeMap 有序集合 | 红黑树底层(0731 已讲) |
| HashMap 基本操作(0731) | Map 综合练习(keySet/entrySet 遍历) | Map 与 Set 的互转、分组统计 |
| 异常处理(0728) | 自定义异常 + 多 catch 块 | 异常链、try-with-resources |