c260803course / exericse / 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 一致。这样排序判定、相等判定、哈希判定三者保持一致,集合的「去重」行为才符合预期。
java.util.Collections 是操作集合的工具类,专门用来对 List / Set / Map 等集合进行排序、查找、填充、转换等操作。
Arrays 是数组的工具类,Collections 就是集合的工具类Collections.sort 是比较器排序在 List 上的应用——传入 Comparator 匿名内部类即可对 List 自定义排序(与 TreeSet 构造传 Comparator 思路一致)// 来源:course/Demo02.java(部分)
// List.of() 是 JDK9+ 提供的快速创建【不可变集合】的方法
// 再用 new ArrayList<>(...) 包装成可变的 ArrayList
List<String> list = new ArrayList<>(List.of("C", "B", "A", "F", "G"));
// 来源:course/Demo02.java
package course;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class Demo02 {
public static void main(String[] args) {
List<String> list = new ArrayList<>(List.of("C", "B", "A", "F", "G"));
// 1. 排序:自然顺序升序——要求每个元素都必须实现 Comparable 接口,重写 compareTo()
Collections.sort(list);
// 2. 排序:自定义方式排序——Comparator 匿名内部类,重写 compare()
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
});
// 3. 逆序:把集合元素顺序反转
Collections.reverse(list);
// 4. 查找:二分查找指定元素,存在返回索引,不存在返回 -1
int i = Collections.binarySearch(list, "C");
// 5. 查找最大 / 最小元素
String max = Collections.max(list);
String min = Collections.min(list);
// 6. 把当前集合转化为不可变集合(不能 add / remove)
List<String> unmodifiableList = Collections.unmodifiableList(list);
// 7. 随机打乱集合元素顺序
Collections.shuffle(list);
// 8. 使用某个对象全部填充元素
Collections.fill(list, "X");
// 9. 交换两个位置的元素
Collections.swap(list, 0, 1);
// 10. 旋转元素:整体向后移动 distance 个位置
Collections.rotate(list, 2);
// 11. 替换指定元素:把旧值全部换成新值
Collections.replaceAll(list, "A", "Z");
}
}
| 方法 | 作用 | 说明 |
|---|---|---|
sort(List) |
自然升序排序 | 元素必须实现 Comparable,否则抛 ClassCastException |
sort(List, Comparator) |
自定义排序 | 与 TreeSet(Comparator) 思路一致,按 compare 返回值排 |
reverse(List) |
逆序 | 把集合元素顺序整体反转 |
binarySearch(List, key) |
二分查找 | 返回元素索引,不存在返回 -1;查找前必须先排序 |
max(List) / min(List) |
最大 / 最小元素 | 元素须可比较(实现 Comparable) |
unmodifiableList(List) |
转不可变集合 | 返回的集合不能增删改,否则抛 UnsupportedOperationException |
shuffle(List) |
随机打乱 | 类似洗牌 |
fill(List, obj) |
全部填充 | 用指定对象替换集合中所有元素 |
swap(List, i, j) |
交换元素 | 交换两个索引位置的元素 |
rotate(List, distance) |
旋转元素 | 元素整体向后移动 distance 个位置 |
replaceAll(List, oldVal, newVal) |
替换元素 | 把所有 oldVal 替换为 newVal |
Collections.sort(list, comparator) 把比较器排序从「集合存入时」扩展到了「已有 List 排序」——二者共用同一套返回值规则(负数存左 / 0 相等 / 正数存右)。sort(List) 的前提:自然排序要求元素实现 Comparable,否则运行时报 ClassCastException——与 TreeSet/TreeMap 存入对象的要求完全一致。binarySearch 使用前提:二分查找基于有序数组,所以查找前必须先 sort,否则结果不可靠;找不到返回 -1。List.of() 的陷阱:返回的是不可变集合,直接 add/remove 会抛 UnsupportedOperationException;Demo02 用 new ArrayList<>(List.of(...)) 包装后才可变。可变参数也叫参数个数可变,作为方法的形参出现——也就是说方法形参的个数是可以改变的。
先回顾两个基础概念:
public void method(int 形参名)setName("张三"),其中的 "张三" 就是实际参数为什么要用可变参数?
之前计算"多个整数相加",有多少个参数就要写多少个方法重载(add(int a, int b) / add(int a, int b, int c) / add(int a, int b, int c, int d)...)。这样方便用户使用,但不利于我们自己定义——写多少个重载都覆盖不完所有情况。有了可变参数,一个方法就能接收任意个数参数。
格式:
访问权限修饰符 返回值类型 方法名(形参类型... 参数名){
// 方法体
}
// 来源:course/Demo03.java
package course;
public class Demo03 {
// 传统写法:几个参数就要重载几个方法
public int add(int a, int b) {
return a + b;
}
/**
* 可变参数:可以实现多个整数累加和
* @param a 就是一个可变参数(实际上等价于 int[] a)
*/
public int add(int... a) { // 本质上等价于 add(int[] a)
int sum = 0;
for (int n : a) {
sum += n;
}
return sum;
}
public static void main(String[] args) {
// 直接传入任意多个整数
int sum = new Demo03().add(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
// 上面这一行,和下面这两行效果一样——可变参数本质就是数组
int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
int sum1 = new Demo03().add(arr);
System.out.println(sum);
}
}
| 注意点 | 说明 |
|---|---|
| 可变参数本质就是一个数组 | int... a 与 int[] a 等价,方法内用 foreach 遍历即可 |
| 可变参数必须放在参数列表最后 | 一个方法有多个参数时,可变参数不能写在固定参数前面 |
| 只能有一个可变参数 | 一个方法最多只能有一个可变参数 |
| 调用方式灵活 | 既可以传 0 个、1 个、多个参数,也可以直接传一个数组 |
int... a 在方法内部就是一个 int[] a,所以遍历方式和数组完全一致。public void method(String name, int... scores)。Lambda 表达式是 JDK 8 引入的、简化匿名内部类的一种写法,让代码更简洁、自由度更高。
标准格式:
(形式参数) -> {代码块}
())->:由英文减号 - 和大于号 > 组成,固定写法,代表指向动作三要素:形式参数、箭头、代码块。
为什么要有 Lambda 表达式? Java 常被诟病"规范太多、太过冗长"。随着 JavaScript / Python / Go 等更灵活的语言流行,Java 为了迎合新的市场需求也开始求变,引入了 Lambda 表达式、函数式接口、Stream 流等更简练的写法。
只有一个抽象方法的接口叫函数式接口。Lambda 表达式本质是另一种形式的匿名内部类,但只能用于简化函数式接口——因为接口里只有一个需要重写的抽象方法,Lambda 的 () 才不会有歧义。
// 来源:course/Swimming.java
// Swimming 接口只有一个抽象方法 swim() → 函数式接口
public interface Swimming {
void swim();
}
// 来源:course/SwimmingTest.java
package course;
public class SwimmingTest {
// 接收 Swimming 接口对象并调用其 swim() 方法
public static void getSwimming(Swimming s) {
s.swim();
}
public static void main(String[] args) {
// 实现 Swimming 接口并重写 swim() 方法,以前有两种方式:
// 1、建立实现类,创建实现类对象(此处略)
// 2、创建 Swimming 的匿名内部类,并重写方法
getSwimming(new Swimming() {
@Override
public void swim() {
System.out.println("自由泳.....自由....免费");
}
});
// 3、使用 Lambda 表达式 —— 其实就是另一种形式的匿名内部类
getSwimming(() -> {
System.out.println("蛙游.....呱....呱");
});
// 匿名内部类:用变量接收
Swimming swimming = new Swimming() {
@Override
public void swim() {
System.out.println("自由泳.....自由....免费");
}
};
swimming.swim();
// Lambda 表达式:用变量接收
Swimming sw = () -> {
System.out.println("蛙游.....呱....呱");
};
sw.swim();
}
}
在数学中,函数就是"有输入量、输出量的一套计算方案",即"拿数据做操作":
Lambda 表达式就是体现函数式编程思想的一种实现方式。
函数式接口:接口中只包含一个抽象方法的接口就是函数式接口。JDK 内置了大量函数式接口,基本都存在于 java.util.function 包中:
| 接口 | 抽象方法形态 | 说明 |
|---|---|---|
Supplier<T> |
无参有返回值 | 返回类型 T |
Consumer<T> |
有参无返回值 | 参数类型 T |
Function<T, R> |
有参有返回值 | 参数类型 T,返回类型 R |
Predicate<T> |
有参有返回值 | 参数类型 T,返回值 boolean(断言) |
UnaryOperator<T> |
有参有返回值 | 接收 T,返回 T(一元运算) |
BinaryOperator<T> |
有参有返回值 | 接收 T, T,返回 T(二元运算) |
Demo04 中总结了三条省略规则:
| 省略规则 | 说明 |
|---|---|
| ① 参数类型可以省略 | 但不能"多个参数只省略其中一个"——要么全写,要么全省 |
| ② 参数有且只有一个,小括号可以省略 | (s) -> {...} 可省略为 s -> {...} |
| ③ 代码块的语句只有一条,可以省略大括号、分号以及 return 关键字 | (a, b) -> { return a + b; } 可省略为 (a, b) -> a + b |
// 来源:course/Addable.java + AddableDemo.java
// 有参有返回值的函数式接口
public interface Addable {
int add(int x, int y);
}
public class AddableDemo {
public static void useAdd(Addable a) {
int sum = a.add(10, 20);
System.out.println(sum);
}
public static void main(String[] args) {
// 完整写法:
// useAdd((a, b) -> {
// return a + b;
// });
// 省略写法:代码块只有一条语句 → 省略大括号、分号、return 关键字
useAdd((a, b) -> a + b); // 两个参数 → 小括号不能省略
}
}
// 来源:course/Flyable.java + FlyableDemo.java
// 有参无返回值的函数式接口
public interface Flyable {
void fly(String s);
}
public class FlyableDemo {
public static void useFlyable(Flyable f) {
f.fly("今天天气不错...是风和日丽的...");
}
public static void main(String[] args) {
// 参数有且只有一个 → 小括号可以省略:s -> {...}
useFlyable(s -> {
System.out.println(s);
System.out.println("长翅膀....飞...");
});
}
}
使用 Lambda 表达式有两个必须满足的前提:
| 对比维度 | 匿名内部类 | Lambda 表达式 |
|---|---|---|
| 需要的类型 | 可以是接口、抽象类、普通具体类 | 只能是接口 |
| 使用限制 | 接口有多个抽象方法也能用 | 必须只有一个抽象方法(函数式接口) |
| 实现原理 | 编译后产生一个独立的 .class 字节码文件 |
编译后不产生单独的 .class 文件,字节码在运行的时候动态生成 |
Swimming 接口里只有一个需要重写的抽象方法,所以 () 能指向的就只有这一个抽象方法,-> 指向的就是重写抽象方法的代码块内容。(形式参数) → 重写方法的参数列表;-> → 指向动作;{代码块} → 重写方法的方法体。Collections.sort(list, (o1, o2) -> o1.getAge() - o2.getAge())。创建
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 是标准做法核心步骤:
new ArrayList<>(List.of("C","B","A","F","G")) 快速创建可变 ListCollections.sort(list) 自然排序 → 观察字母升序Collections.sort(list, new Comparator<String>(){...}) 自定义排序(如降序)Collections.reverse(list) / Collections.shuffle(list) 观察顺序变化Collections.binarySearch(list, "C")(先排序再查找)、Collections.max/min(list)Collections.unmodifiableList(list) 转不可变集合后尝试 add(),观察异常Collections.fill(list, "X") / Collections.swap(list, 0, 1) / Collections.replaceAll(list, "A", "Z")思考:
Collections.sort(list) 为什么要求元素实现 Comparable?不实现会怎样?—— 工具类内部要比较元素大小,不实现则运行时抛 ClassCastExceptionbinarySearch 查找前为什么要先排序?—— 二分查找基于有序序列,未排序结果不可靠List.of() 返回的集合为什么不能 add/remove?—— 返回的是不可变集合,直接修改抛 UnsupportedOperationException,需用 new ArrayList<>(...) 包装核心步骤:
add(int... a),方法内用 foreach 累加求和add(1, 2)、add(1, 2, 3)、add(1, 2, 3, 4, 5),体会"一个方法接收任意个数参数"int[] arr = {...}; add(arr),验证"可变参数本质就是数组"method(String name, int... scores),验证可变参数必须放在最后思考:
int... a 和 int[] a 有什么关系?—— 本质相同,int... a 是 int[] a 的语法糖核心步骤:
Swimming(只有一个抽象方法 void swim())swim()() -> { System.out.println("..."); }思考:
->、代码块() 才能唯一确定指向它(o1, o2) -> o1.getAge() - o2.getAge(),后续 Stream 也会大量使用核心步骤:
Addable(int add(int x, int y)),用 Lambda 实现 useAdd((a, b) -> a + b)——验证"单语句省略大括号/分号/return"Flyable(void fly(String s)),用 Lambda 实现 s -> {...}——验证"单参数省略小括号"Collections.sort(list, (s1, s2) -> s1.getAge() - s2.getAge()),观察省略后的极简写法java.util.function 包,说出 Supplier / Consumer / Function / Predicate 四个接口的形态思考:
(a, b) -> a + b 能省略 return?—— 代码块只有一条语句且是表达式时,表达式结果自动作为返回值对应今日课堂练习的参考解答已写入授课代码:
| 练习 | 参考代码路径 |
|---|---|
| 进阶 2:员工信息管理系统(Employee 实现 Comparable + equals/hashCode 一致性 + 匿名内部类 Comparator 按姓名排序) | exericse/exericse02/Employee.java + EmployeeSystem.java |
Employee 参考解答要点:
// 来源:exericse/exericse02/Employee.java
// 自然排序:按薪资降序,薪资相同再按工号
@Override
public int compareTo(Employee o) {
int result = Double.compare(o.getSalary(), this.salary); // 薪资降序
return result == 0 ? this.id.compareTo(o.getId()) : result;
}
// equals / hashCode:基于全部属性(salary + id + name + dept)
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return Double.compare(salary, employee.salary) == 0
&& Objects.equals(id, employee.id)
&& Objects.equals(name, employee.name)
&& Objects.equals(dept, employee.dept);
}
@Override
public int hashCode() {
return Objects.hash(id, name, salary, dept);
}
EmployeeSystem 参考解答要点:
// 来源:exericse/exericse02/EmployeeSystem.java
// 1. 默认自然排序:薪资降序 → 薪资相同按工号;工号相同的自动去重
TreeSet<Employee> treeSet = new TreeSet<>();
treeSet.add(new Employee("E001", "张三", 12000, "技术部"));
// ... 共添加 5 名员工
// 迭代器遍历输出
Iterator<Employee> iterator = treeSet.iterator();
while (iterator.hasNext()) {
Employee employee = iterator.next();
System.out.println(employee.getId() + "-" + employee.getName() + "-" + employee.getSalary() + "-" + employee.getDept());
}
// 2. 临时需求:通讯录按姓名排序 → 重新创建 TreeSet 传入按姓名排序的匿名 Comparator
treeSet = new TreeSet<>(new Comparator<Employee>() {
@Override
public int compare(Employee o1, Employee o2) {
return o1.getName().compareTo(o2.getName());
}
});
原理思考:
E001-张三(12000) 和 E005-刘七(12000) 薪资相同却能都存入?—— compareTo 薪资相同后继续比较工号(E001 ≠ E005),返回值不为 0,不视为重复equals 基于全部属性(薪资+工号+姓名+部门),而 compareTo 只按薪资+工号判定——若两名员工薪资工号相同但姓名不同,会出现 compareTo == 0 但 equals == false。这与官方推荐(compareTo == 0 时 equals 返回 true)略有出入,实际开发中应尽量让两者判定口径保持一致。| 前置知识 | 当前知识 | 后续知识 |
|---|---|---|
| 匿名内部类(0727) | Lambda 表达式 / 函数式接口 | Stream 流、方法引用 |
| 接口与多态(0723/0724) | JDK 内置函数式接口(java.util.function) | 方法引用、Stream 高阶函数 |
| 集合体系与泛型(0729) | TreeSet / TreeMap 有序集合 | 红黑树底层(0731 已讲) |
| HashMap 基本操作(0731) | Map 综合练习(keySet/entrySet 遍历) | Map 与 Set 的互转、分组统计 |
| 异常处理(0728) | 自定义异常 + 多 catch 块 | 异常链、try-with-resources |
| Arrays 工具类(0716) | Collections 集合工具类 | Stream API、Java 8 新特性 |
| Comparator 比较器排序(本节 3) | Collections.sort(list, comparator) | 函数式接口 & Lambda 简化比较器 |
| 数组与方法参数(0717) | 可变参数 Varargs | 不定长参数 & 命令行参数、Stream 收集 |