Browse Source

docs: 更新20260728课堂笔记,同步代码变更——新增Object类(Object根父类/equals重写规范/hashCode约定/toString重写/Objects工具类)、异常代码包重构(course→course.exception)、练习解引用(exercise/exercise01); 更新README笔记与代码表

WanJL 1 hour ago
parent
commit
d30f06db8d

+ 2 - 2
README.md

@@ -33,7 +33,7 @@
 | 2026-07-23 | [抽象类 + final + 模板方法 + 代码块 + 接口(含JDK8+新特性):抽象类定义与特点、final 类/方法/变量、模板方法模式、三种代码块、接口定义与实现(Usb→LogiMouse/ROGMouse)、接口 vs 抽象类对比、JDK8默认方法static方法、JDK9私有方法](./课堂笔记/01-JavaSE基础-笔记/20260723-笔记.md) |
 | 2026-07-24 | [多态 + 多态中的转型 + 内部类:多态概念与前提、多态成员访问特点、向上转型/向下转型、instanceof类型判断、多态的好处(开闭原则)、抽象类与多态结合(Animal→Dog;Person→Student/Teacher综合案例)、Usb接口多态案例(NoteBook接入鼠标/猫)、内部类概念与使用场景、成员内部类、私有内部类设计原则](./课堂笔记/01-JavaSE基础-笔记/20260724-笔记.md) |
 | 2026-07-27 | [匿名内部类 + Java API 与 String 类 + Math + 日期时间:内部类三大分类、匿名内部类四种使用方式、综合案例(USB/餐具/学生排序/计算器/动物叫声);API概念、String内存模型/不可变性/可变字符串StringBuffer&StringBuilder/文本块TextBlock JDK15+/String常用方法大全;Math数学类常用方法;日期时间类(Date/LocalDate/LocalTime/LocalDateTime/DateTimeFormatter)](./课堂笔记/01-JavaSE基础-笔记/20260727-笔记.md) |
-| 2026-07-28 | [Java 异常处理:异常概念、Throwable 体系结构、编译期/运行时异常、JVM 默认处理、try-catch 多 catch 块、异常常用方法(getMessage/printStackTrace/getCause/fillInStackTrace)、finally 一定会执行的代码(资源关闭)、throws 抛出、鲁棒性;自定义异常(继承 Exception/继承 RuntimeException);throw 手动抛出 vs throws 声明异常;课后作业回顾 — String 分割去重/统计、LocalDate 天数差计算](./课堂笔记/01-JavaSE基础-笔记/20260728-笔记.md) |
+| 2026-07-28 | [Java 异常处理:异常概念、Throwable 体系结构、编译期/运行时异常、JVM 默认处理、try-catch 多 catch 块、异常常用方法、finally 资源关闭、throws 抛出、鲁棒性;自定义异常、throw/throws;Object 类:equals/hashCode/toString 重写、Objects 工具类;课后作业回顾 — String 分割去重/统计、LocalDate 天数差计算](./课堂笔记/01-JavaSE基础-笔记/20260728-笔记.md) |
 
 ## 二、授课代码
 
@@ -53,7 +53,7 @@
 | 2026-07-23 | 抽象类 + final + 模板方法 + 代码块 + 接口(含JDK8+新特性):抽象类定义与特点、final 类/方法/变量、模板方法模式(MyOneDayTemplate)、三种代码块(局部/构造/静态)、接口定义与实现(Usb→LogiMouse/ROGMouse)、接口 vs 抽象类对比、JDK8默认方法/静态方法(MyDefaultInterface→MyDefaultInterfaceImpl)、JDK9私有方法(抽取多个默认方法公共代码)、Test05 测试类 | `c260723` | [`course`](./授课代码/c260723/src/course/) |
 | 2026-07-24 | 多态概念与前提、多态成员访问特点、向上转型/向下转型、instanceof运算符、多态的好处(开闭原则)、抽象类+多态(Animal→Dog、Person→Student/Teacher→Test03.runPerson())、接口多态(Usb→Mouse/Cat→NoteBook)、内部类概念与使用场景 | `c260724` | [`course/polymorphism`](./授课代码/c260724/src/course/polymorphism/) / [`course/internalClass`](./授课代码/c260724/src/course/internalClass/) |
 | 2026-07-27 | 匿名内部类:内部类三大分类概念回顾、匿名内部类语法格式与本质、三种使用方式(多态接收变量赋值 / 方法参数传递 / 匿名对象直接调用)、配套接口/抽象类定义(Usb / Tableware / Compute / Sort)、综合案例(NoteBook USB设备 / Person 餐具 / 学生排序比较 / Calculator计算器 / Animal动物叫声);Java API:API概念、String类(内存模型/构造方法/==与equals/常用方法大全/不可变性)、可变字符串(StringBuffer/SringBuilder)、文本块TextBlock JDK15+;Math数学类;日期时间类(Date/LocalDate/LocalTime/LocalDateTime/DateTimeFormatter) | `c260727` | [`course/anonymousInternalClass`](./授课代码/c260727/src/course/anonymousInternalClass/) / [`course/api`](./授课代码/c260727/src/course/api/) / [`course/api/string`](./授课代码/c260727/src/course/api/string/) / [`course/api/math`](./授课代码/c260727/src/course/api/math/) / [`course/api/date`](./授课代码/c260727/src/course/api/date/) / [`exercise/exercise01`](./授课代码/c260727/src/exercise/exercise01/) / [`exercise/exercise06`](./授课代码/c260727/src/exercise/exercise06/) / [`exercise/exercise07`](./授课代码/c260727/src/exercise/exercise07/) / [`homework_0724/p2_myarray`](./授课代码/c260727/src/homework_0724/p2_myarray/) |
-| 2026-07-28 | Java 异常处理(Demo01异常常用方法 + Demo02自定义异常 + Demo03 throw/throws + Demo04 finally + Person+AgeOutOfBoundsException) + 课后作业回顾(String工具 + 日期数学计算) + Exercise07异常常用方法综合 | `c260728` | [`course`](./授课代码/c260728/src/course/) / [`homework260727`](./授课代码/c260728/src/homework260727/) / [`exercise/exercise07`](./授课代码/c260728/src/exercise/exercise07/) |
+| 2026-07-28 | 异常处理(Demo01~Demo04 + Person+AgeOutOfBoundsException) + Object类(Demo01+Person 重写equals/hashCode/toString) + 课后作业回顾(String工具 + 日期数学) + Exercises | `c260728` | [`course/api`](./授课代码/c260728/src/course/api/) / [`course/exception`](./授课代码/c260728/src/course/exception/) / [`homework260727`](./授课代码/c260728/src/homework260727/) / [`exercise/exercise01`](./授课代码/c260728/src/exercise/exercise01/) / [`exercise/exercise07`](./授课代码/c260728/src/exercise/exercise07/) |
 
 ## 三、课堂练习题
 

+ 53 - 0
授课代码/c260728/src/course/api/Demo01.java

@@ -0,0 +1,53 @@
+package course.api;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title Demo01
+ * @description Object类
+ * @create 2026/7/28
+ */
+public class Demo01 {
+    public static void main(String[] args) {
+        /*
+            java中所有类的父类 就是java.lang.Object类
+
+            常用的方法:
+                1、equals() 比较两个对象是否相同,一般我们会重写equals方法,让equals方法按照我们的【要求和规则】进行比较。
+                    Object类中比较的时候 ==是否相等 也就是地址值(哈希码值)是否相等
+                2、hashCode():返回对象的哈希码值,Object返回的都是该对象在内存中的地址
+                3、toString(): 把对象内容按照格式转为字符串,
+                Object默认的是转换为 :
+                    包名.类名 @ 哈希码值的十六进制
+                一般我们也会重写toString方法,方便我们输出对象的属性变量。
+
+                在重写equals的时候一般都会一起重写hashCode()方法。
+                如果equals()比较不想等,那么两个对象的hashCode()方法返回的整数值也不要相等。
+
+            所以为了符合我们开发的业务需求,一般都会为实体类(可以代表一个业务实体的类)重写equals()和 hashCode()方法
+
+            我们使用IDEA开发,不需要自己手动重写上面的三个方法,只需要使用快捷键生成即可。
+            当然,你要想自己写也可以。
+
+         */
+
+        Person p=new Person("张三",25);
+        Person p1=new Person("李四",12);
+        System.out.println(p==p1);  //false
+        System.out.println(p.equals(p1));//false    当重写了equals方法后,比较就返回true
+        String s1=new String("张三");
+        String s2=new String("张三");
+        System.out.println(s1==s2);     //false
+        System.out.println(s1.equals(s2));  // true
+
+        System.out.println(p.hashCode());
+        System.out.println(p1.hashCode());
+
+        Person p2=new Person();
+        System.out.println(p2.hashCode());
+        System.out.println(p2);
+        System.out.println(p);
+        System.out.println(p1);
+
+    }
+}

+ 59 - 0
授课代码/c260728/src/course/api/Person.java

@@ -0,0 +1,59 @@
+package course.api;
+
+import java.util.Objects;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title Person
+ * @description
+ * @create 2026/7/28
+ */
+public class Person {
+    private String name;
+    private int age;
+
+    public Person() {
+    }
+
+    public Person(String name, int age) {
+        this.name = name;
+        this.age = age;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public int getAge() {
+        return age;
+    }
+
+    public void setAge(int age) {
+        this.age = age;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == null || getClass() != o.getClass()) return false;
+        Person person = (Person) o;
+        return age == person.age && Objects.equals(name, person.name);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(name, age);
+    }
+
+    @Override
+    public String toString() {
+        return "Person{" +
+                "name='" + name + '\'' +
+                ", age=" + age +
+                '}';
+    }
+}

+ 1 - 1
授课代码/c260728/src/course/AgeOutOfBoundsException.java → 授课代码/c260728/src/course/exception/AgeOutOfBoundsException.java

@@ -1,4 +1,4 @@
-package course;
+package course.exception;
 
 import java.security.PrivilegedActionException;
 

+ 1 - 1
授课代码/c260728/src/course/Demo01.java → 授课代码/c260728/src/course/exception/Demo01.java

@@ -1,4 +1,4 @@
-package course;
+package course.exception;
 
 /**
  * @author WanJl

+ 1 - 1
授课代码/c260728/src/course/Demo02.java → 授课代码/c260728/src/course/exception/Demo02.java

@@ -1,4 +1,4 @@
-package course;
+package course.exception;
 
 /**
  * @author WanJl

+ 1 - 1
授课代码/c260728/src/course/Demo03.java → 授课代码/c260728/src/course/exception/Demo03.java

@@ -1,4 +1,4 @@
-package course;
+package course.exception;
 
 /**
  * @author WanJl

+ 1 - 1
授课代码/c260728/src/course/Demo04.java → 授课代码/c260728/src/course/exception/Demo04.java

@@ -1,4 +1,4 @@
-package course;
+package course.exception;
 
 import java.util.Scanner;
 

+ 1 - 1
授课代码/c260728/src/course/Person.java → 授课代码/c260728/src/course/exception/Person.java

@@ -1,4 +1,4 @@
-package course;
+package course.exception;
 
 /**
  * @author WanJl

+ 30 - 0
授课代码/c260728/src/exercise/exercise01/AgeInvalidException.java

@@ -0,0 +1,30 @@
+package exercise.exercise01;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title AgeInvalidException
+ * @description 年龄异常(年龄必须在 0~150 之间)
+ * @create 2026/7/28
+ */
+public class AgeInvalidException extends Exception{
+    protected AgeInvalidException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
+
+    public AgeInvalidException(Throwable cause) {
+        super(cause);
+    }
+
+    public AgeInvalidException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public AgeInvalidException(String message) {
+        super(message);
+    }
+
+    public AgeInvalidException() {
+        super();
+    }
+}

+ 30 - 0
授课代码/c260728/src/exercise/exercise01/PasswordEmptyException.java

@@ -0,0 +1,30 @@
+package exercise.exercise01;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title PasswordEmptyException
+ * @description 密码不能为空异常
+ * @create 2026/7/28
+ */
+public class PasswordEmptyException extends Exception{
+    protected PasswordEmptyException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
+
+    public PasswordEmptyException(Throwable cause) {
+        super(cause);
+    }
+
+    public PasswordEmptyException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public PasswordEmptyException(String message) {
+        super(message);
+    }
+
+    public PasswordEmptyException() {
+        super();
+    }
+}

+ 30 - 0
授课代码/c260728/src/exercise/exercise01/PasswordWeakException.java

@@ -0,0 +1,30 @@
+package exercise.exercise01;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title PasswordWeakException
+ * @description 密码强度异常(必须包含数字和字母,长度 >= 6)
+ * @create 2026/7/28
+ */
+public class PasswordWeakException extends Exception{
+    protected PasswordWeakException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
+
+    public PasswordWeakException(Throwable cause) {
+        super(cause);
+    }
+
+    public PasswordWeakException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public PasswordWeakException(String message) {
+        super(message);
+    }
+
+    public PasswordWeakException() {
+        super();
+    }
+}

+ 162 - 0
授课代码/c260728/src/exercise/exercise01/RegisterService.java

@@ -0,0 +1,162 @@
+package exercise.exercise01;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title RegisterService
+ * @description 注册服务类
+ * @create 2026/7/28
+ */
+public class RegisterService {
+
+    /**
+     * 验证用户名
+     * @param username 用户名
+     * @throws UsernameEmptyException 用户名为空
+     * @throws UsernameLengthException 用户名长度不在 3~12 之间
+     */
+    public static void validateUsername(String username)
+            throws UsernameEmptyException, UsernameLengthException {
+        // 1. 如果 username 为 null 或 trim() 后长度为 0,抛出 UsernameEmptyException
+        //    提示信息:"用户名不能为空!"
+        if (username==null||username.trim().length()==0){
+            throw new UsernameEmptyException("用户名不能为空!");
+        }
+        // 2. 如果 username.length() < 3 或 > 12,抛出 UsernameLengthException
+        //    提示信息:"用户名长度必须在 3~12 个字符之间!"
+        if (username.trim().length()<3||username.trim().length()>12){
+            throw new UsernameLengthException("用户名长度必须在 3~12 个字符之间!");
+        }
+        // 请补充代码
+    }
+
+    /**
+     * 验证密码
+     * @param password 密码
+     * @throws PasswordEmptyException 密码为空
+     * @throws PasswordWeakException 密码强度不足
+     */
+    public static void validatePassword(String password)
+            throws PasswordEmptyException, PasswordWeakException {
+        // 1. 如果 password 为 null 或 trim() 后长度为 0,抛出 PasswordEmptyException
+        //    提示信息:"密码不能为空!"
+        if (password==null||password.trim().length()==0){
+            throw new PasswordEmptyException("密码不能为空!");
+        }
+        // 2. 如果 password.length() < 6,抛出 PasswordWeakException
+        //    提示信息:"密码长度不能少于 6 位!"
+        if (password.trim().length()<6){
+            throw new PasswordWeakException("密码长度不能少于 6 位!");
+        }
+        // 3. 如果 password 不包含数字和字母(判断技巧:使用正则或循环遍历逐个字符检查),
+        //    抛出 PasswordWeakException
+        //    提示信息:"密码必须包含数字和字母!"
+        // hel123
+        boolean b1=false;
+        boolean b2=false;
+        for (int i = 0; i < password.length(); i++) {
+            char c = password.charAt(i);
+            if (c>=48&&c<=57){  //判断是否包含数字
+                b1=true;
+            }
+            if (c>=65&&c<=90||c>=97&&c<=122){   //判断是否包含字母
+                b2=true;
+            }
+        }
+        //如果有一个不包含,就抛异常
+        if (b1==false||b2==false) {
+            throw new PasswordWeakException("密码必须包含数字和字母!");
+        }
+
+        // 请补充代码(建议:使用循环遍历 + 两个 boolean 标记)
+    }
+
+    /**
+     * 验证年龄
+     * @param age 年龄字符串
+     * @throws NumberFormatException 年龄不是数字
+     * @throws AgeInvalidException 年龄超出范围
+     */
+    public static void validateAge(String age)
+            throws NumberFormatException, AgeInvalidException {
+        // 1. 使用 Integer.parseInt(age) 转换为 int
+        //    如果 age 不是有效数字,NumberFormatException 会自动抛出(无需手动处理)
+        int i = Integer.parseInt(age);
+
+        // 2. 如果 age < 0 或 age > 150,抛出 AgeInvalidException
+        //    提示信息:"年龄必须在 0~150 之间!"
+        if (i<0||i>150){
+            throw new AgeInvalidException("年龄必须在 0~150 之间!");
+        }
+
+        // 请补充代码
+    }
+
+    /**
+     * 注册用户
+     * @param username 用户名
+     * @param password 密码
+     * @param age 年龄
+     */
+    public static void register(String username, String password, String age) {
+        // 要求:
+        // 1. 使用 try-catch-finally 调用各验证方法
+        // 2. 按顺序验证:用户名 → 密码 → 年龄
+        // 3. 每个验证都有独立的 catch 块,输出对应的错误提示
+        // 4. 如果所有验证都通过,输出:"注册成功!欢迎 " + username
+        // 5. 注意:validateAge() 可能抛出 NumberFormatException,也需要捕获
+        // 6. 【新增】添加 finally 块,无论注册成功还是失败,都输出:
+        //    "—— 注册操作日志:用户名=" + username + ",结果=" + (是否成功 ? "成功" : "失败")
+        //    提示:可以在 try 块成功时用一个 boolean 变量标记成功,finally 中读取该变量
+        boolean b=false;
+        try{
+            validateUsername(username);
+            validatePassword(password);
+            validateAge(age);
+            b=true;
+        } catch (UsernameLengthException e) {
+            System.out.println(e.getMessage());
+        } catch (UsernameEmptyException e) {
+            System.out.println(e.getMessage());
+        } catch (PasswordWeakException e) {
+            System.out.println(e.getMessage());
+        } catch (PasswordEmptyException e) {
+            System.out.println(e.getMessage());
+        }catch (NumberFormatException e) {
+            System.out.println(e.getMessage());
+        } catch (AgeInvalidException e) {
+            System.out.println(e.getMessage());
+        } finally {
+            System.out.println("—— 注册操作日志:用户名=" + username + ",结果=" + (b ? "成功" : "失败"));
+        }
+
+
+    }
+
+    public static void main(String[] args) {
+        // 测试用例
+        System.out.println("========== 测试 1:正常注册 ==========");
+        register("张三丰", "abc123", "25");
+
+        System.out.println("\n========== 测试 2:用户名为空 ==========");
+        register("", "abc123", "25");
+
+        System.out.println("\n========== 测试 3:用户名太短 ==========");
+        register("12", "abc123", "25");
+
+        System.out.println("\n========== 测试 4:密码太短 ==========");
+        register("张三丰", "12", "25");
+
+        System.out.println("\n========== 测试 5:密码不包含数字 ==========");
+        register("张三丰", "abcdef", "25");
+
+        System.out.println("\n========== 测试 6:年龄不是数字 ==========");
+        register("张三丰", "abc123", "二十五");
+
+        System.out.println("\n========== 测试 7:年龄超出范围 ==========");
+        register("张三丰", "abc123", "200");
+
+        System.out.println("\n========== 测试 8:综合错误(多错误按顺序只报第一个)==========");
+        register("", "", "abc");  // 应该只报用户名错误,不会检查密码和年龄
+    }
+}

+ 30 - 0
授课代码/c260728/src/exercise/exercise01/UsernameEmptyException.java

@@ -0,0 +1,30 @@
+package exercise.exercise01;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title UsernameEmptyException
+ * @description 用户名不能为空异常
+ * @create 2026/7/28
+ */
+public class UsernameEmptyException extends Exception{
+    protected UsernameEmptyException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
+
+    public UsernameEmptyException(Throwable cause) {
+        super(cause);
+    }
+
+    public UsernameEmptyException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public UsernameEmptyException(String message) {
+        super(message);
+    }
+
+    public UsernameEmptyException() {
+        super();
+    }
+}

+ 30 - 0
授课代码/c260728/src/exercise/exercise01/UsernameLengthException.java

@@ -0,0 +1,30 @@
+package exercise.exercise01;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title UsernameLengthException
+ * @description 用户名长度异常(长度必须在 3~12 个字符之间)
+ * @create 2026/7/28
+ */
+public class UsernameLengthException extends Exception{
+    protected UsernameLengthException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
+
+    public UsernameLengthException(Throwable cause) {
+        super(cause);
+    }
+
+    public UsernameLengthException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public UsernameLengthException(String message) {
+        super(message);
+    }
+
+    public UsernameLengthException() {
+        super();
+    }
+}

+ 193 - 0
授课代码/c260728/src/exercise/exercise01/题目.md

@@ -0,0 +1,193 @@
+## 第 1 题:综合应用——用户注册验证系统(异常处理综合)⭐⭐⭐⭐
+
+### 需求说明
+
+开发一个用户注册验证系统,对用户输入的注册信息进行校验。当输入不合法时,使用 **自定义异常** 和 **多 catch 处理** 给出明确提示。
+
+### 自定义异常类
+
+定义以下自定义异常类(继承 `Exception` —— 编译期异常):
+
+```java
+// 用户名不能为空异常
+class UsernameEmptyException extends Exception {
+    public UsernameEmptyException(String message) {
+        super(message);
+    }
+}
+
+// 用户名长度异常(长度必须在 3~12 个字符之间)
+class UsernameLengthException extends Exception {
+    public UsernameLengthException(String message) {
+        super(message);
+    }
+}
+
+// 密码不能为空异常
+class PasswordEmptyException extends Exception {
+    public PasswordEmptyException(String message) {
+        super(message);
+    }
+}
+
+// 密码强度异常(必须包含数字和字母,长度 >= 6)
+class PasswordWeakException extends Exception {
+    public PasswordWeakException(String message) {
+        super(message);
+    }
+}
+
+// 年龄异常(年龄必须在 0~150 之间)
+class AgeInvalidException extends Exception {
+    public AgeInvalidException(String message) {
+        super(message);
+    }
+}
+```
+
+### RegisterService 类
+
+```java
+public class RegisterService {
+    
+    /**
+     * 验证用户名
+     * @param username 用户名
+     * @throws UsernameEmptyException 用户名为空
+     * @throws UsernameLengthException 用户名长度不在 3~12 之间
+     */
+    public static void validateUsername(String username) 
+            throws UsernameEmptyException, UsernameLengthException {
+        // 1. 如果 username 为 null 或 trim() 后长度为 0,抛出 UsernameEmptyException
+        //    提示信息:"用户名不能为空!"
+        // 2. 如果 username.length() < 3 或 > 12,抛出 UsernameLengthException
+        //    提示信息:"用户名长度必须在 3~12 个字符之间!"
+        // 请补充代码
+    }
+    
+    /**
+     * 验证密码
+     * @param password 密码
+     * @throws PasswordEmptyException 密码为空
+     * @throws PasswordWeakException 密码强度不足
+     */
+    public static void validatePassword(String password) 
+            throws PasswordEmptyException, PasswordWeakException {
+        // 1. 如果 password 为 null 或 trim() 后长度为 0,抛出 PasswordEmptyException
+        //    提示信息:"密码不能为空!"
+        // 2. 如果 password.length() < 6,抛出 PasswordWeakException
+        //    提示信息:"密码长度不能少于 6 位!"
+        // 3. 如果 password 不包含数字或字母(判断技巧:使用正则或循环遍历逐个字符检查),
+        //    抛出 PasswordWeakException
+        //    提示信息:"密码必须包含数字和字母!"
+        // 请补充代码(建议:使用循环遍历 + 两个 boolean 标记)
+    }
+    
+    /**
+     * 验证年龄
+     * @param age 年龄字符串
+     * @throws NumberFormatException 年龄不是数字
+     * @throws AgeInvalidException 年龄超出范围
+     */
+    public static void validateAge(String age) 
+            throws NumberFormatException, AgeInvalidException {
+        // 1. 使用 Integer.parseInt(age) 转换为 int
+        //    如果 age 不是有效数字,NumberFormatException 会自动抛出(无需手动处理)
+        // 2. 如果 age < 0 或 age > 150,抛出 AgeInvalidException
+        //    提示信息:"年龄必须在 0~150 之间!"
+        // 请补充代码
+    }
+    
+    /**
+     * 注册用户
+     * @param username 用户名
+     * @param password 密码
+     * @param age 年龄
+     */
+    public static void register(String username, String password, String age) {
+        // 要求:
+        // 1. 使用 try-catch-finally 调用各验证方法
+        // 2. 按顺序验证:用户名 → 密码 → 年龄
+        // 3. 每个验证都有独立的 catch 块,输出对应的错误提示
+        // 4. 如果所有验证都通过,输出:"注册成功!欢迎 " + username
+        // 5. 注意:validateAge() 可能抛出 NumberFormatException,也需要捕获
+        // 6. 【新增】添加 finally 块,无论注册成功还是失败,都输出:
+        //    "—— 注册操作日志:用户名=" + username + ",结果=" + (是否成功 ? "成功" : "失败")
+        //    提示:可以在 try 块成功时用一个 boolean 变量标记成功,finally 中读取该变量
+        
+        // 请补充代码
+    }
+    
+    public static void main(String[] args) {
+        // 测试用例
+        System.out.println("========== 测试 1:正常注册 ==========");
+        register("张三丰", "abc123", "25");
+        
+        System.out.println("\n========== 测试 2:用户名为空 ==========");
+        register("", "abc123", "25");
+        
+        System.out.println("\n========== 测试 3:用户名太短 ==========");
+        register("12", "abc123", "25");
+        
+        System.out.println("\n========== 测试 4:密码太短 ==========");
+        register("张三丰", "12", "25");
+        
+        System.out.println("\n========== 测试 5:密码不包含数字 ==========");
+        register("张三丰", "abcdef", "25");
+        
+        System.out.println("\n========== 测试 6:年龄不是数字 ==========");
+        register("张三丰", "abc123", "二十五");
+        
+        System.out.println("\n========== 测试 7:年龄超出范围 ==========");
+        register("张三丰", "abc123", "200");
+        
+        System.out.println("\n========== 测试 8:综合错误(多错误按顺序只报第一个)==========");
+        register("", "", "abc");  // 应该只报用户名错误,不会检查密码和年龄
+    }
+}
+```
+
+### 预期输出(含 finally 操作日志)
+
+```
+========== 测试 1:正常注册 ==========
+注册成功!欢迎 张三丰
+—— 注册操作日志:用户名=张三丰,结果=成功
+
+========== 测试 2:用户名为空 ==========
+用户名不能为空!
+—— 注册操作日志:用户名=,结果=失败
+
+========== 测试 3:用户名太短 ==========
+用户名长度必须在 3~12 个字符之间!
+—— 注册操作日志:用户名=12,结果=失败
+
+========== 测试 4:密码太短 ==========
+密码长度不能少于 6 位!
+—— 注册操作日志:用户名=张三丰,结果=失败
+
+========== 测试 5:密码不包含数字 ==========
+密码必须包含数字和字母!
+—— 注册操作日志:用户名=张三丰,结果=失败
+
+========== 测试 6:年龄不是数字 ==========
+年龄格式错误:二十五 不是有效的数字!
+—— 注册操作日志:用户名=张三丰,结果=失败
+
+========== 测试 7:年龄超出范围 ==========
+年龄必须在 0~150 之间!
+—— 注册操作日志:用户名=张三丰,结果=失败
+
+========== 测试 8:综合错误 ==========
+用户名不能为空!
+—— 注册操作日志:用户名=,结果=失败
+```
+
+### 思考题
+
+1. 本练习中,自定义异常都继承了 `Exception`(编译期异常),这有什么影响?如果改为继承 `RuntimeException`,代码会有什么变化?
+2. 为什么 `register()` 方法中,每个验证方法都有自己的 catch 块,而不是用一个大 `catch (Exception e)` 统一处理?这样做的好处是什么?
+3. 测试 8 中,用户名、密码、年龄同时出错,为什么只报了用户名的错误?这体现了什么设计思想?
+4. 自定义异常在实际项目中有哪些应用场景?什么时候应该自定义异常而不是使用 Java 内置异常?
+5. **【finally 思考】** 本练习中 `register()` 方法使用 `finally` 输出了操作日志。如果不用 `finally`,把日志输出放在 try 块末尾和每个 catch 块中分别写一次,是否能达到同样效果?这样做有什么弊端?(提示:代码重复、遗漏风险)
+6. **【finally 思考】** 如果 try 块中有 `return` 语句,`finally` 块还会执行吗?请编写一个小测试验证你的结论。

+ 142 - 13
课堂笔记/01-JavaSE基础-笔记/20260728-笔记.md

@@ -372,18 +372,135 @@ public class Demo03 {
 
 ---
 
+## 四、Object 类(java.lang.Object)—— 所有类的根父类
+
+### 4.1 Object 类概述
+
+`java.lang.Object` 是 Java 中 **所有类的父类**(根父类)。Java 中任何一个类如果没有显式继承其他类,默认都继承自 `Object` 类。
+
+#### 常用方法
+
+| 方法 | 说明 |
+|------|------|
+| `boolean equals(Object obj)` | 比较两个对象是否"相等" |
+| `int hashCode()` | 返回对象的哈希码值 |
+| `String toString()` | 返回对象的字符串表示形式 |
+
+> 📄 来源:`course/api/Demo01.java`
+
+### 4.2 equals() 方法
+
+#### Object 默认实现
+
+在 `Object` 类中,`equals()` 的默认实现使用 `==` 比较,即 **比较两个对象的引用地址**(哈希码值)是否相等。
+
+```java
+// Object 类中 equals 的默认实现
+public boolean equals(Object obj) {
+    return (this == obj);
+}
+```
+
+#### 重写 equals() 的场景
+
+在实际开发中,我们通常希望 **按照业务规则** 来判断两个对象是否相等。例如,两个 `Person` 对象如果姓名和年龄相同,就应该视为同一个人。
+
+```java
+Person p = new Person("张三", 25);
+Person p1 = new Person("李四", 12);
+System.out.println(p == p1);            // false — == 比较的是地址值
+System.out.println(p.equals(p1));       // false — 未重写时与 == 相同
+
+String s1 = new String("张三");
+String s2 = new String("张三");
+System.out.println(s1 == s2);           // false — 两个不同的 String 对象
+System.out.println(s1.equals(s2));      // true — String 类已重写 equals
+```
+
+> 📄 来源:`course/api/Demo01.java`
+
+#### 重写 equals() 的规范
+
+```java
+@Override
+public boolean equals(Object o) {
+    if (o == null || getClass() != o.getClass()) return false;
+    Person person = (Person) o;
+    return age == person.age && Objects.equals(name, person.name);
+}
+```
+
+> 📄 来源:`course/api/Person.java`
+
+### 4.3 hashCode() 方法
+
+`hashCode()` 返回对象的 **哈希码值**,在 `Object` 类中默认返回的是该对象在内存中的地址。
+
+#### 重写规则
+
+- **如果两个对象通过 `equals()` 比较相等,那么它们的 `hashCode()` 值必须相等**
+- 重写 `equals()` 时一般都要同时重写 `hashCode()`
+
+```java
+@Override
+public int hashCode() {
+    return Objects.hash(name, age);  // 使用 Objects.hash() 工具方法
+}
+```
+
+> 📄 来源:`course/api/Person.java`
+
+#### Objects 工具类(JDK7+)
+
+`java.util.Objects` 提供了很多安全操作对象的方法:
+
+| 方法 | 说明 |
+|------|------|
+| `Objects.equals(Object a, Object b)` | 安全的 equals 比较(自动处理 null) |
+| `Objects.hash(Object... values)` | 根据多个字段生成哈希码 |
+| `Objects.requireNonNull(Object obj)` | 检查对象是否为 null,为 null 则抛异常 |
+
+```java
+Objects.equals(name, person.name);  // 等价于 name != null && name.equals(person.name)
+Objects.hash(name, age);            // 根据 name 和 age 生成哈希值
+```
+
+> 📄 来源:`course/api/Person.java`
+
+### 4.4 toString() 方法
+
+`toString()` 将对象转换为字符串表示。Object 默认实现为 `包名.类名 @ 哈希码值的十六进制`。
+
+```java
+// 默认输出如:course.api.Person@3feba861
+
+// 重写后:
+@Override
+public String toString() {
+    return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';
+}
+```
+
+> 📄 来源:`course/api/Person.java`
+
+### 4.5 IDEA 快速生成
+
+使用 IntelliJ IDEA 开发时,通过 **右键 → Generate → equals() and hashCode() / toString()** 即可快速生成。
+
+> 📄 来源:`course/api/Demo01.java`
+
 ---
 
-## 四、课后作业回顾 — StringUtilTest 字符串工具测试
+## 、课后作业回顾 — StringUtilTest 字符串工具测试
 
-### 4.1 需求说明
+### 5.1 需求说明
 
 给定有前后空格和重复项的字符串数据 `"  张三, 李四, 王五, 赵六, 张三, 钱七  "`,需要:
 1. 去除每个元素的前后空格
 2. 去重(去除重复的姓名)
 3. 统计每个姓名的出现次数
 
-### 4.2 相关 API
+### 5.2 相关 API
 
 | 方法 | 说明 |
 |------|------|
@@ -391,7 +508,7 @@ public class Demo03 {
 | `String.trim()` | 去除字符串前后的空白字符 |
 | `Arrays.toString(Object[] a)` | 将数组转换为字符串表示形式 |
 
-### 4.3 核心算法
+### 5.3 核心算法
 
 #### 三步去重算法
 
@@ -460,20 +577,20 @@ for (int i = 0; i < newArr.length; i++) {
 
 ---
 
-## 、课后作业回顾 — DataMathTest 日期时间计算
+## 、课后作业回顾 — DataMathTest 日期时间计算
 
-### 5.1 需求说明
+### 6.1 需求说明
 
 计算当前日期距离 2026-12-31 还有多少天。
 
-### 5.2 相关 API
+### 6.2 相关 API
 
 | 方法 | 说明 |
 |------|------|
 | `LocalDate.of(int year, int month, int dayOfMonth)` | 创建指定日期的 `LocalDate` 对象 |
 | `LocalDate.toEpochDay()` | 返回从 1970-01-01 到当前日期的天数 |
 
-### 5.3 代码实现
+### 6.3 代码实现
 
 ```java
 LocalDate localDate = LocalDate.of(2026, 7, 27);
@@ -494,9 +611,9 @@ System.out.println("距离 2026-12-31 还有:" + (epochDay - now) + "天");
 
 ---
 
-## 、随堂练习要点
+## 、随堂练习要点
 
-### 6.1 异常处理练习要点
+### 7.1 异常处理练习要点
 
 - 理解 `Throwable` → `Exception` → `RuntimeException` 的继承体系
 - 区分编译期异常和运行时异常
@@ -510,7 +627,7 @@ System.out.println("距离 2026-12-31 还有:" + (epochDay - now) + "天");
 - 掌握自定义异常的创建步骤(继承 Exception → 无参/有参构造方法)
 - 体会异常处理对程序 **鲁棒性(健壮性)** 的提升
 
-### 6.2 字符串处理综合练习要点
+### 7.2 字符串处理综合练习要点
 
 - 使用 `split(",")` 分割带分隔符的字符串
 - 使用 `trim()` 去除字符串前后空格
@@ -518,18 +635,30 @@ System.out.println("距离 2026-12-31 还有:" + (epochDay - now) + "天");
 - 掌握元素出现次数的统计算法
 - 理解 `equals()` 在字符串比较中的必要性
 
-### 6.3 日期时间计算练习要点
+### 7.3 日期时间计算练习要点
 
 - 使用 `LocalDate.of()` 创建指定日期
 - 使用 `toEpochDay()` 计算两个日期之间的天数差
 - 了解其他可选方案:`until()`、`Period.between()`、`ChronoUnit.DAYS.between()`
 
+### 7.4 Object 类练习要点
+
+- 理解 `Object` 是所有类的根父类
+- 掌握 `equals()` 的重写规范:判断 null → 判断类型 → 比较关键字段
+- 掌握 `hashCode()` 与 `equals()` 的约定:equals 相等则 hashCode 必须相等
+- 掌握 `toString()` 重写,便于输出对象属性
+- 熟悉 `Objects` 工具类的常用方法:`Objects.equals()`、`Objects.hash()`、`Objects.requireNonNull()`
+- 学会使用 IDEA 快捷键生成 `equals()`、`hashCode()`、`toString()`
+
 ---
 
-## 七、拓展阅读
+## 、拓展阅读
 
 1. **Java 异常处理最佳实践** — 何时使用 checked vs unchecked exception,何时自定义异常
 2. **try-with-resources** — JDK7+ 自动关闭资源的异常处理方式(`try(Resource r = new Resource()){...}`),是 `finally` 关闭资源方式的进化版,无需手动写 `finally` 块
+3. **`Objects` 工具类源码分析** — 阅读 `java.util.Objects` 的 `equals()`、`hash()`、`requireNonNull()` 源码,理解其安全机制
+4. **`equals()` 与 `hashCode()` 约定详解** — 理解为什么重写 equals 必须重写 hashCode(HashMap/HashSet 等集合类的依赖)
+5. **`toString()` 与日志框架** — 在 SLF4J/Logback 等日志框架中,`toString()` 的正确重写对日志输出非常重要
 3. **异常链(Exception Chaining)** — 通过 `initCause()` 或构造方法将底层异常包装为高层异常
 4. **`Objects.requireNonNull()`** — 一种优雅避免 NullPointerException 的方式(JDK7+)
 5. **`Period` 与 `Duration`** — Java 8 日期时间 API 中更灵活的时间间隔计算