Procházet zdrojové kódy

feat: 新增20260806课程内容——File文件操作与IO流入门(字节流FileInputStream/FileOutputStream/FileCopy文件复制),学生信息管理系统大作业(homework0804:entity实体/exception异常/manager管理器/RankUtil工具/main主程序),含课后作业(文件复制器)/课堂笔记/课堂练习

WanJL před 1 týdnem
rodič
revize
953c2e16c8
24 změnil soubory, kde provedl 2491 přidání a 0 odebrání
  1. 75 0
      授课代码/c260806/src/course/Demo01.java
  2. 50 0
      授课代码/c260806/src/course/Demo02.java
  3. 51 0
      授课代码/c260806/src/course/FileCopy.java
  4. 52 0
      授课代码/c260806/src/course/FileInputStreamTest.java
  5. 59 0
      授课代码/c260806/src/course/FileOutputStreamTest.java
  6. 53 0
      授课代码/c260806/src/homework0804/entity/Course.java
  7. 41 0
      授课代码/c260806/src/homework0804/entity/Person.java
  8. 77 0
      授课代码/c260806/src/homework0804/entity/Score.java
  9. 124 0
      授课代码/c260806/src/homework0804/entity/Student.java
  10. 12 0
      授课代码/c260806/src/homework0804/entity/Studyable.java
  11. 12 0
      授课代码/c260806/src/homework0804/entity/Teachable.java
  12. 77 0
      授课代码/c260806/src/homework0804/entity/Teacher.java
  13. 13 0
      授课代码/c260806/src/homework0804/exception/CourseNotFoundException.java
  14. 13 0
      授课代码/c260806/src/homework0804/exception/DuplicateStudentException.java
  15. 13 0
      授课代码/c260806/src/homework0804/exception/ScoreOutOfBoundsException.java
  16. 13 0
      授课代码/c260806/src/homework0804/exception/StudentNotFoundException.java
  17. 158 0
      授课代码/c260806/src/homework0804/main/StudentSystemApp.java
  18. 61 0
      授课代码/c260806/src/homework0804/manager/CourseManager.java
  19. 87 0
      授课代码/c260806/src/homework0804/manager/ScoreManager.java
  20. 87 0
      授课代码/c260806/src/homework0804/manager/StudentManager.java
  21. 80 0
      授课代码/c260806/src/homework0804/util/RankUtil.java
  22. 96 0
      课后作业/2026年8月6日-作业.md
  23. 1091 0
      课堂笔记/01-JavaSE基础-笔记/20260806-笔记.md
  24. 96 0
      课堂练习题/20260806-File文件操作练习.md

+ 75 - 0
授课代码/c260806/src/course/Demo01.java

@@ -0,0 +1,75 @@
+package course;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title Demo01
+ * @description 文件操作类 File
+ * @create 2026/8/6
+ */
+public class Demo01 {
+    /*
+        java.io.File
+        File类,是文件和目录路径名的抽象表示(类)
+        文件和目录(文件夹)可以通过File封装成对象
+        对于File来说,他的封装并不是一个真正存在的文件,只是一个路径名,他是可以存在,也可以不存在,
+        需要将来通过这个路径进行具体的操作,把文件路径内容转换为真实存在的。
+
+        public File(String pathname) 通过给定的路径名字符串转换为抽象路径名,来创建新的File对象
+        public File(String parent, String child)    从父路径字符串和子路径字符串创建一个新的File对象
+        public File(File parent, String child)  从父抽象路径名(File对象)和子路径名字符串创建一个新的File对象
+
+        路径分隔符:Windows是  \       Linux、UNIX、安卓、IOS:都是 /
+
+        绝对路径和相对路径
+        绝对路径是一个完整的路径,从盘符(C盘、D盘...)或根目录(/)开始
+        绝对路径:可以理解为地球的经纬度或者是--国家/省/市/区/县/镇/乡/村/屯... 或者是 你的快递地址
+        E:/abc/a.txt
+
+        相对路径是一个简化的路径,相对于某个位置,比如相对于当前项目下的路径。
+        相对路径:可以理解为前后左右。我的前后左右,张三的前后左右,我的前面的前面
+        ./  当前项目的同一层级路径
+        ../ 当前项目的上一层级路径
+        创建文件及目录
+        public boolean createNewFile()  当具有该名称的文件不存在的时候,创建由抽象路径名命名的新文件,会抛异常IOException
+        public boolean mkdir()      创建由抽象路径名命名的新文件夹
+        public boolean mkdirs()     创建由抽象路径名命名的新文件夹,多级创建。如果父目录不存在就创建父目录
+        删除文件或目录
+        public boolean delete()     删除由抽象路径名命名的文件或目录
+        判断
+        public boolean isDirectory() 判断是否是文件夹
+        public boolean isFile()     判断是否是文件
+        public boolean exists()     判断该路径是否真实存在
+
+        获取功能
+        public String getAbsolutePath() 获取绝对路径
+        public String getPath() 获取路径
+        public String getName() 获取文件名或目录名
+        public File[] listFiles()   返回路径目录的所有文件和目录组成的数组
+
+     */
+    public static void main(String[] args) throws IOException {
+        File file=new File("E:\\edg");
+        //file.createNewFile();
+        //boolean b = file.mkdirs();
+        boolean d = file.delete();
+
+
+
+
+        //System.out.println("是否创建成功:"+b);
+        System.out.println("是否删除成功:"+d);
+
+        System.out.println("绝对路径:"+file.getAbsolutePath());
+        System.out.println("相对路径:"+file.getPath());
+        System.out.println("是否是文件:"+file.isFile());
+        System.out.println("是否是文件夹:"+file.isDirectory());
+        System.out.println("是否存在:"+file.exists());
+        System.out.println("文件名:"+file.getName());
+        System.out.println("里面有哪些文件:"+ Arrays.toString(file.listFiles()));
+    }
+}

+ 50 - 0
授课代码/c260806/src/course/Demo02.java

@@ -0,0 +1,50 @@
+package course;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title Demo02
+ * @description IO流
+ * @create 2026/8/6
+ */
+public class Demo02 {
+    /*
+        IO流
+        IO:输入输出(Input/Output)
+        流:Stream是一种抽象概念,是对数据传输的总称,也就是说数据在设备直接的传输称为流,流的本质就是数据传输
+        IO流就是用来处理设备之间的数据传输问题的,常见应用场景:文件赋值、文件上传、文件下载。
+
+        IO流的分类:
+            按照数据的流向分类(相对于内存来说):
+            输入流:向内存输入数据,读数据。
+            输出流:从内存往外输出数据,写数据。
+        按照数据类型分类:
+            字节流(就是传输的每个数据都是byte类型)
+                |- 字节输入流
+                |- 字节输出流
+            字符流(就是传输的每个数据都是char类型)
+                |- 字符输入流
+                |- 字符输出流
+        IO流的使用场景推荐:
+            如果是纯文本文件(.txt),建议使用字符流,因为1个字符等于2个字节
+            除了纯文本文件之外的任何文件包括文本、图片、视频、音频、等等任何文件,都建议是字节流。(Word、Excel、PPT、md、png、mp3、mp4、rmvb....)
+            如果不确定文件类型,就是有字节流
+
+        InputStream和OutputStream
+
+        OutputStream:这是一个抽象类,表示所有字节输出流类的父类
+        InputStream:这是一个抽象类,表示所有字节输入流类的父类
+        他们的子类类名比较有特点:子类的名称都是以父类类名作为子类的后缀。
+        文件字节输入流:FileInputStream
+        文件字节输出流:FileOutputStream
+
+        使用字节输出流的步骤:
+            1、创建字节输出流对象
+            2、调用字节输出流的写数据的方法
+            3、释放资源,关闭流
+        使用字节输入流的步骤:
+            1、创建字节输入流对象
+            2、调用字节输入流的读数据的方法
+            3、释放资源,关闭流
+     */
+}

+ 51 - 0
授课代码/c260806/src/course/FileCopy.java

@@ -0,0 +1,51 @@
+package course;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title FileCopy
+ * @description
+ * @create 2026/8/6
+ */
+public class FileCopy {
+    /**
+     * 文件赋值
+     * @param src 原路径
+     * @param desc 目的路径
+     * @throws IOException
+     */
+    public static void copy(String src,String desc) throws IOException {
+        //1、创建输入流对象
+        FileInputStream fis=new FileInputStream(src);
+        //2、创建输出流对象
+        FileOutputStream fos=new FileOutputStream(desc);
+
+        //3、创建一个字节数组 长度1024
+        byte[] bytes=new byte[4096];    //每一次最多一次复制1KB  最多读取1024byte,最多写入1024byte
+        int length; //每次读取并复制的字节数量
+        while ((length= fis.read(bytes))!=-1){  //循环读取
+            //循环写入
+            // bytes 是要写入的字节数组,0 从0开始。length 写入到本次赋值的有效字节数量
+            fos.write(bytes,0,length);
+        }
+
+        //释放资源
+        fos.close();
+        fis.close();
+    }
+
+    public static void main(String[] args) throws IOException {
+        String src="E:\\授课资料\\四福科技-爱扣钉\\2026年\\J260713\\授课视频\\01-JavaSE\\2026年07月13日-视频-Java基础语法&git下载安装\\14_git下载安装.mp4";
+        String desc="D:\\abc.mp4";
+        long startTime=System.currentTimeMillis();//获取当前的毫秒数,从格林威治时间的1970年1月1日0点0分0秒开始计时到现在的毫秒数
+        copy(src,desc);
+        long endTime=System.currentTimeMillis();
+        System.out.println(endTime-startTime);
+
+    }
+}

+ 52 - 0
授课代码/c260806/src/course/FileInputStreamTest.java

@@ -0,0 +1,52 @@
+package course;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.charset.Charset;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title FileInputStreamTest
+ * @description 文件字节输入流:FileInputStream
+ * @create 2026/8/6
+ */
+public class FileInputStreamTest {
+    /*
+
+    读取的方法:
+        read ()
+        read (byte[]b)  一次读取数据存入到字节数组b中
+        read (byte[] b, int off, int len)
+     */
+    public static void main(String[] args) {
+        //使用字节输入流的步骤:
+        FileInputStream fis=null;
+        try {
+            //1、创建字节输入流对象
+            fis=new FileInputStream("E:/我的文件.txt");
+            //2、调用字节输入流的读数据的方法
+            byte[] bytes = new byte[1024];  //1KB
+            System.out.println(fis.read());//读取一个字节并输出
+            int length; //定义每一次读取的字节数量
+            //fis.read(bytes) 的返回值,是读取到的有效的字节的长度
+            while ((length=fis.read(bytes))!=-1){
+                System.out.println(new String(bytes,0,length, Charset.forName("UTF-8")));
+            }
+
+        } catch (IOException e) {
+            e.printStackTrace();
+        }finally {//里面写 close()关闭流
+            try {
+                //3、释放资源,关闭流
+                fis.close();
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+
+
+    }
+}

+ 59 - 0
授课代码/c260806/src/course/FileOutputStreamTest.java

@@ -0,0 +1,59 @@
+package course;
+
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title FileOutputStreamTest
+ * @description 文件字节输出流:FileOutputStream
+ * @create 2026/8/6
+ */
+public class FileOutputStreamTest {
+    /*
+    构造方法:
+        File0utputStream (File file)    创建文件字节输出流对象,传入参数为File对象
+        FileOutputStream (String name)  创建文件字节输出流对象,传入参数字符串路径
+        File0utputStream (File file,boolean append)    创建文件字节输出流对象,传入参数为File对象,如果append为true,就追加内容
+        FileOutputStream (String name,boolean append)  创建文件字节输出流对象,传入参数字符串路径,如果append为true,就追加内容
+
+        write(int b)    写一个字节
+        write(byte[] b) 写一个字节数组的内容
+        write(byte[] b, int off, int len)   写一个字节数组的内容,从off开始,长度为len
+
+        换行符:
+        Linux:\n
+        Windows:\r\n
+        macOS:\r
+
+     */
+    public static void main(String[] args) throws IOException {
+        //使用字节输出流的步骤:
+        //1、创建字节输出流对象
+        FileOutputStream fos=new FileOutputStream("E:/我的文件.txt",true);
+        //2、调用字节输出流的写数据的方法
+        //写入一个字节
+        fos.write(97);
+        fos.write(98);
+        fos.write(99);
+
+        //写入一个字节数组
+        byte[] arr={65,66,67,68,69,70,71,72};
+        fos.write(arr);
+        //写一个字节数组的内容,从off开始,长度为len
+        fos.write(arr,3,3);
+        fos.write("\r\n".getBytes());
+        String s= """
+                两只黄鹂鸣翠柳,
+                一行白鹭上青天,
+                窗含西岭千秋雪,
+                门泊自挂东南枝。
+                """;
+        byte[] bytes = s.getBytes();    //根据字符串转换为字节数组
+        fos.write(bytes);
+        //3、释放资源,关闭流
+        fos.close();
+    }
+}

+ 53 - 0
授课代码/c260806/src/homework0804/entity/Course.java

@@ -0,0 +1,53 @@
+package homework0804.entity;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title Course
+ * @description
+ * @create 2026/8/6
+ */
+public class Course {
+    private String courseId;    // 课程编号,如 C001
+    private String courseName;  // 课程名称,如 "JavaSE 基础"
+    private int credit;         // 学分
+
+    public Course(String courseId, String courseName, int credit) {
+        this.courseId = courseId;
+        this.courseName = courseName;
+        this.credit = credit;
+    }
+
+    // TODO: getter / setter
+
+    public String getCourseId() {
+        return courseId;
+    }
+
+    public void setCourseId(String courseId) {
+        this.courseId = courseId;
+    }
+
+    public String getCourseName() {
+        return courseName;
+    }
+
+    public void setCourseName(String courseName) {
+        this.courseName = courseName;
+    }
+
+    public int getCredit() {
+        return credit;
+    }
+
+    public void setCredit(int credit) {
+        this.credit = credit;
+    }
+
+    // TODO: toString —— 输出 "C001-JavaSE 基础-3学分"
+
+    @Override
+    public String toString() {
+        return courseId+"-"+courseName+"-"+credit;
+    }
+}

+ 41 - 0
授课代码/c260806/src/homework0804/entity/Person.java

@@ -0,0 +1,41 @@
+package homework0804.entity;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title Person
+ * @description
+ * @create 2026/8/6
+ */
+public abstract class Person {
+    private String name;   // 姓名
+    private int age;       // 年龄
+
+    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;
+    }
+    // TODO: 抽象方法 —— 返回角色描述("学生" / "教师")
+    public abstract String getRole();
+
+    @Override
+    public String toString() {
+        return "姓名:" + name + ",年龄:" + age;
+    }
+}

+ 77 - 0
授课代码/c260806/src/homework0804/entity/Score.java

@@ -0,0 +1,77 @@
+package homework0804.entity;
+
+import homework0804.exception.ScoreOutOfBoundsException;
+
+import java.util.Objects;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title Score
+ * @description
+ * @create 2026/8/6
+ */
+public class Score {
+    private String studentId;   // 学号
+    private String courseId;    // 课程编号
+    private double score;       // 成绩(0~100)
+
+    public Score(String studentId, String courseId, double score) throws ScoreOutOfBoundsException {
+        this.studentId = studentId;
+        this.courseId = courseId;
+        setScore(score);        // 在 setter 中校验并抛出异常
+    }
+
+    // TODO: getter / setter —— setScore 中校验:score < 0 || score > 100 时
+    //    throw new ScoreOutOfBoundsException("成绩必须在 0~100 之间,当前值:" + score);
+
+    public String getStudentId() {
+        return studentId;
+    }
+
+    public void setStudentId(String studentId) {
+        this.studentId = studentId;
+    }
+
+    public String getCourseId() {
+        return courseId;
+    }
+
+    public void setCourseId(String courseId) {
+        this.courseId = courseId;
+    }
+
+    public double getScore() {
+        return score;
+    }
+
+    public void setScore(double score) throws ScoreOutOfBoundsException {
+        if (score < 0 || score > 100)
+            throw new ScoreOutOfBoundsException("成绩必须在 0~100 之间,当前值:" + score);
+        this.score = score;
+    }
+
+    // TODO: equals / hashCode —— 以 studentId + courseId 判定(同一学生同一课程视为一条成绩)
+
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof Score score)) return false;
+        return Objects.equals(studentId, score.studentId) && Objects.equals(courseId, score.courseId);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(studentId, courseId);
+    }
+
+    // TODO: toString
+
+    @Override
+    public String toString() {
+        return "Score{" +
+                "studentId='" + studentId + '\'' +
+                ", courseId='" + courseId + '\'' +
+                ", score=" + score +
+                '}';
+    }
+}

+ 124 - 0
授课代码/c260806/src/homework0804/entity/Student.java

@@ -0,0 +1,124 @@
+package homework0804.entity;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title Student
+ * @description
+ * @create 2026/8/6
+ */
+public class Student extends Person implements Comparable<Student>, Studyable {
+    private String id;                // 学号,如 S001
+    private String clazz;             // 班级,如 "Java2301"
+    private List<Score> scores;       // 该学生的成绩列表
+
+    public Student(String name, int age, String id, String clazz) {
+        super(name, age);             // 调用父类构造方法(继承知识点)
+        this.id = id;
+        this.clazz = clazz;
+        this.scores = new ArrayList<>();
+    }
+
+    // TODO: getter / setter
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getClazz() {
+        return clazz;
+    }
+
+    public void setClazz(String clazz) {
+        this.clazz = clazz;
+    }
+
+    public List<Score> getScores() {
+        return scores;
+    }
+
+    public void setScores(List<Score> scores) {
+        this.scores = scores;
+    }
+
+
+    // TODO: addScore(Score s) —— 向成绩列表添加一条成绩
+    public void addScore(Score s) {
+        scores.add(s);
+    }
+
+    // TODO: getTotalScore() —— 遍历 scores 累加总分(注意:scores 为空返回 0)
+    public double getTotalScore() {
+        Iterator<Score> iterator = scores.iterator();
+        double total = 0;
+        while (iterator.hasNext()) {
+            total += iterator.next().getScore();
+        }
+        return total;
+    }
+
+    // TODO: getAvgScore()   —— 总分 / 成绩条数(保留两位小数;无成绩返回 0)
+    public double getAvgScore() {
+        return getTotalScore() / getScoreCount();
+    }
+
+    // TODO: getScoreCount() —— 返回成绩条数
+    public int getScoreCount(){
+        return scores.size();
+    }
+
+    // TODO: 实现抽象方法 getRole() —— 返回 "学生"
+    @Override
+    public String getRole() {
+        return "学生";
+    }
+
+
+    // TODO: 实现接口方法 study() —— 输出 "xxx 同学正在努力学习..."
+
+    @Override
+    public void study() {
+        System.out.println(this.getName()+" 同学正在努力学习...");
+    }
+
+    // TODO: 重写 compareTo —— 按平均分【降序】,平均分相同再按学号
+    //   提示:int result = Double.compare(o.getAvgScore(), this.getAvgScore());
+    //         return result == 0 ? this.id.compareTo(o.getId()) : result;
+
+    @Override
+    public int compareTo(Student o) {
+        int result = Double.compare(o.getAvgScore(), this.getAvgScore());
+        return result == 0 ? this.id.compareTo(o.getId()) : result;
+    }
+
+
+    // TODO: 重写 equals / hashCode —— 以【学号】判定同一人
+    //   提示:equals 用 Objects.equals(id, student.id);hashCode 用 Objects.hash(id)
+
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof Student student)) return false;
+        return Objects.equals(id, student.id);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(id);
+    }
+
+    // TODO: 重写 toString —— 输出 学号/姓名/年龄/班级/平均分
+
+    @Override
+    public String toString() {
+        return "学号:" + id + ", 姓名:'" + this.getName() + ", 年龄:" + this.getAge() +
+                ", 班级:" + this.getClazz() + ", 平均分" + this.getAvgScore();
+    }
+}

+ 12 - 0
授课代码/c260806/src/homework0804/entity/Studyable.java

@@ -0,0 +1,12 @@
+package homework0804.entity;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title Studyable
+ * @description
+ * @create 2026/8/6
+ */
+public interface Studyable {
+    void study();   // 学习
+}

+ 12 - 0
授课代码/c260806/src/homework0804/entity/Teachable.java

@@ -0,0 +1,12 @@
+package homework0804.entity;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title Teachable
+ * @description
+ * @create 2026/8/6
+ */
+public interface Teachable {
+    void teach();   // 授课
+}

+ 77 - 0
授课代码/c260806/src/homework0804/entity/Teacher.java

@@ -0,0 +1,77 @@
+package homework0804.entity;
+
+import java.util.Objects;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title Teacher
+ * @description
+ * @create 2026/8/6
+ */
+public class Teacher extends Person implements Teachable {
+    private String id;        // 工号,如 T001
+    private Course course;    // 教授的课程(对象关联)
+
+    public Teacher(String name, int age, String id, Course course) {
+        super(name, age);
+        this.id = id;
+        this.course = course;
+    }
+
+    // TODO: getter / setter
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public Course getCourse() {
+        return course;
+    }
+
+    public void setCourse(Course course) {
+        this.course = course;
+    }
+
+    // TODO: 实现 getRole() —— 返回 "教师"
+
+    @Override
+    public String getRole() {
+        return "教师";
+    }
+
+    // TODO: 实现 teach() —— 输出 "xxx 老师正在讲授《课程名》..."
+
+    @Override
+    public void teach() {
+        System.out.println(this.getName()+"老师正在讲授《"+this.course.getCourseName()+"》...");
+    }
+
+    // TODO: 重写 equals / hashCode —— 以【工号】判定
+
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof Teacher teacher)) return false;
+        return Objects.equals(id, teacher.id);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(id);
+    }
+
+    // TODO: 重写 toString
+
+    @Override
+    public String toString() {
+        return "Teacher{" +
+                "id='" + id + '\'' +
+                ", course=" + course +
+                '}';
+    }
+}
+

+ 13 - 0
授课代码/c260806/src/homework0804/exception/CourseNotFoundException.java

@@ -0,0 +1,13 @@
+package homework0804.exception;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title CourseNotFoundException
+ * @description
+ * @create 2026/8/6
+ */
+public class CourseNotFoundException extends Exception {
+    public CourseNotFoundException() { }
+    public CourseNotFoundException(String message) { super(message); }
+}

+ 13 - 0
授课代码/c260806/src/homework0804/exception/DuplicateStudentException.java

@@ -0,0 +1,13 @@
+package homework0804.exception;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title DuplicateStudentException
+ * @description
+ * @create 2026/8/6
+ */
+public class DuplicateStudentException extends Exception {
+    public DuplicateStudentException() { }
+    public DuplicateStudentException(String message) { super(message); }
+}

+ 13 - 0
授课代码/c260806/src/homework0804/exception/ScoreOutOfBoundsException.java

@@ -0,0 +1,13 @@
+package homework0804.exception;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title ScoreOutOfBoundsException
+ * @description
+ * @create 2026/8/6
+ */
+public class ScoreOutOfBoundsException extends Exception {
+    public ScoreOutOfBoundsException() { }
+    public ScoreOutOfBoundsException(String message) { super(message); }
+}

+ 13 - 0
授课代码/c260806/src/homework0804/exception/StudentNotFoundException.java

@@ -0,0 +1,13 @@
+package homework0804.exception;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title StudentNotFoundException
+ * @description
+ * @create 2026/8/6
+ */
+public class StudentNotFoundException extends Exception {
+    public StudentNotFoundException() { }
+    public StudentNotFoundException(String message) { super(message); }
+}

+ 158 - 0
授课代码/c260806/src/homework0804/main/StudentSystemApp.java

@@ -0,0 +1,158 @@
+package homework0804.main;
+
+import homework0804.entity.Course;
+import homework0804.entity.Student;
+import homework0804.entity.Teacher;
+import homework0804.exception.DuplicateStudentException;
+import homework0804.exception.StudentNotFoundException;
+import homework0804.manager.CourseManager;
+import homework0804.manager.ScoreManager;
+import homework0804.manager.StudentManager;
+
+import java.util.Scanner;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title StudentSystemApp
+ * @description
+ * @create 2026/8/6
+ */
+public class StudentSystemApp {
+    public static void main(String[] args) {
+        // 1. 创建各管理类对象
+        StudentManager sm = new StudentManager();
+        CourseManager cm = new CourseManager();
+        ScoreManager scm = new ScoreManager();
+
+        // 2. 初始化少量示例数据(方便演示):
+        //    学生:张三/18/S001/Java2301、李四/19/S002/Java2301、王五/20/S003/Java2302
+        Student s1 = new Student("张三", 18, "S001", "Java2301");
+        Student s2 = new Student("李四", 19, "S002", "Java2301");
+        Student s3 = new Student("王五", 20, "S003", "Java2302");
+
+        //    课程:C001-JavaSE 基础-3、C002-MySQL 数据库-2、C003-数据结构-3
+        Course c1 = new Course("C001", "JavaSE 基础", 3);
+        Course c2 = new Course("C002", "MySQL 数据库", 2);
+        Course c3 = new Course("C003", "数据结构", 3);
+        //    教师:王老师/35/T001/C001
+        Teacher teacher = new Teacher("王老师", 35, "T001", c1);
+
+        {   //代码块
+            try {
+                sm.addStudent(s1);
+                sm.addStudent(s2);
+                sm.addStudent(s3);
+                cm.addCourse(c1);
+                cm.addCourse(c2);
+                cm.addCourse(c3);
+
+            } catch (DuplicateStudentException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+
+
+        Scanner sc = new Scanner(System.in);
+
+        // 3. 主循环 + 主菜单(while + switch)
+        one:
+        while (true) {
+            System.out.println("===== 学生信息管理系统 =====");
+            System.out.println("1. 学生管理");
+            System.out.println("2. 课程管理");
+            System.out.println("3. 成绩管理");
+            System.out.println("4. 成绩排名");
+            System.out.println("5. 高级查询(挑战·选做)");
+            System.out.println("0. 退出");
+            System.out.println("请输入你的选择:");
+            switch (sc.next()) {
+                case "1" -> {
+                    two:
+                    while (true) {
+                        System.out.println("===== 学生管理 =====");
+                        System.out.println("1. 添加学生");
+                        System.out.println("2. 删除学生");
+                        System.out.println("3. 查找学生");
+                        System.out.println("4. 查看所有学生");
+                        System.out.println("0. 返回上级");
+                        switch (sc.next()) {
+                            case "1" -> {
+                                System.out.println("请输入姓名:");
+                                String name = sc.next();
+                                System.out.println("请输入年龄:");
+                                int age = sc.nextInt();
+                                System.out.println("请输入学号:");
+                                String id = sc.next();
+                                System.out.println("请输入班级:");
+                                String clazz = sc.next();
+                                Student student = new Student(name, age, id, clazz);
+                                try {
+                                    sm.addStudent(student);
+                                } catch (DuplicateStudentException e) {
+                                    System.out.println(e.getMessage());
+                                }
+                            }
+                            case "2" -> {
+
+                                System.out.println("请输入要删除的学号:");
+                                String id = sc.next();
+                                try {
+                                    sm.removeById(id);
+                                } catch (StudentNotFoundException e) {
+                                    System.out.println(e.getMessage());
+                                }
+                            }
+                            case "3" -> {
+                                System.out.println("请输入要查找的学号:");
+                                String id = sc.next();
+                                Student student = sm.findById(id);
+                                System.out.println(student);
+                            }
+                            case "4" -> {
+                                for (Student student : sm.getStudents()) {
+                                    System.out.println(student);
+                                }
+                            }
+                            case "0" -> {
+                                System.out.println("返回上一级");
+                                break two;
+                            }
+                        }
+                    }
+                }
+                case "2" -> {
+                }
+                case "3" -> {
+                }
+                case "4" -> {
+                }
+                case "5" -> {
+                }
+                case "0" -> {
+                }
+            }
+            //    主菜单:
+            //    ===== 学生信息管理系统 =====
+            //    1. 学生管理
+            //    2. 课程管理
+            //    3. 成绩管理
+            //    4. 成绩排名
+            //    5. 高级查询(挑战·选做)
+            //    0. 退出
+            //    请输入你的选择:
+
+            // 4. 二级菜单(以学生管理为例):
+            //    ===== 学生管理 =====
+            //    1. 添加学生
+            //    2. 删除学生
+            //    3. 查找学生
+            //    4. 查看所有学生
+            //    0. 返回上级
+
+            // 5. 每个功能都用 Scanner 接收键盘输入,并 try-catch 处理自定义异常
+            //    (例如:添加重复学号 → 捕获 DuplicateStudentException 并提示)
+        }
+    }
+}

+ 61 - 0
授课代码/c260806/src/homework0804/manager/CourseManager.java

@@ -0,0 +1,61 @@
+package homework0804.manager;
+
+import homework0804.entity.Course;
+import homework0804.exception.CourseNotFoundException;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title CourseManager
+ * @description
+ * @create 2026/8/6
+ */
+public class CourseManager {
+    private ArrayList<Course> courses;
+
+    public CourseManager() {
+        this.courses = new ArrayList<>();
+    }
+
+    // TODO: addCourse(Course c) —— 添加课程(课程编号重复则不允许)
+    public void addCourse(Course c){
+        try {
+            findById(c.getCourseId());  //没找到就抛异常,意味着没有课程编号相同
+        } catch (CourseNotFoundException e) {
+            courses.add(c);//就添加
+        }
+    }
+
+
+    // TODO: findById(String courseId) —— 按课程编号查找,返回 Course;找不到抛 CourseNotFoundException
+    public Course findById(String courseId) throws CourseNotFoundException {
+        for (Course c:courses){
+            if (c.getCourseId().equals(courseId)){
+                return c;
+            }
+        }
+        throw new CourseNotFoundException();
+    }
+
+    // TODO: removeById(String courseId) —— 按课程编号删除(找不到抛 CourseNotFoundException)
+    public void removeById(String courseId){
+        try {
+            findById(courseId);  //没找到就抛异常,意味着没有课程编号相同
+        } catch (CourseNotFoundException e) {
+            courses.remove(courseId);//就添删除
+        }
+    }
+    // TODO: printAll() —— 遍历输出所有课程
+    public void printAll(){
+        courses.stream().forEach(System.out::println);
+    }
+
+    // TODO: getCourses()
+    public List getCourses(){
+        return courses;
+    }
+
+}

+ 87 - 0
授课代码/c260806/src/homework0804/manager/ScoreManager.java

@@ -0,0 +1,87 @@
+package homework0804.manager;
+
+import homework0804.entity.Course;
+import homework0804.entity.Score;
+import homework0804.entity.Student;
+import homework0804.exception.ScoreOutOfBoundsException;
+
+import java.util.*;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title ScoreManager
+ * @description
+ * @create 2026/8/6
+ */
+public class ScoreManager {
+    private ArrayList<Score> scores;                    // 成绩台账
+    private HashMap<String, List<Score>> scoreMap;      // 学号 → 该学生的成绩列表(Map 知识点)
+
+    public ScoreManager() {
+        this.scores = new ArrayList<>();
+        this.scoreMap = new HashMap<>();
+    }
+
+    // TODO: addScore(Student stu, Course cou, double sc) —— 录入一条成绩
+    //   要求:
+    //   1) new Score(stu.getId(), cou.getCourseId(), sc) —— 成绩越界会抛 ScoreOutOfBoundsException
+    //   2) 加入全局成绩台账 scores.add(score)
+    //   3) 学生侧记录成绩:stu.addScore(score)
+    //   4) 更新 scoreMap:按学号分组(若 map 中还没有该学号,先 put 一个新的 ArrayList,再 add)
+    //   5) 输出"录入成功:张三 的 JavaSE 基础 成绩 = 88.5"
+
+    public void addScore(Student stu, Course cou, double sc) throws ScoreOutOfBoundsException {
+        Score score = new Score(stu.getId(), cou.getCourseId(), sc);
+        this.scores.add(score); //加入全局成绩台账 scores.add(score)
+        stu.addScore(score);    //学生侧记录成绩:stu.addScore(score)
+
+        //判断学生id是否在map集合中存在。
+        if (scoreMap.containsKey(stu.getId())) { //存在
+            //从map中通过键获取值 根据学号获取这个学生的所有成绩列表
+            List<Score> list = scoreMap.get(stu.getId());
+            list.add(score);//新加入新的成绩
+
+        }else { //不存在这个学生,
+            List<Score> scoreList=new ArrayList<>();
+            //先 put 一个新的 ArrayList
+            scoreMap.put(stu.getId(),scoreList);
+            //再 add
+            scoreList.add(score);
+        }
+    }
+
+
+
+    // TODO: printAll() —— 遍历输出成绩台账(迭代器或 foreach)
+
+
+
+
+    // TODO: getStudentTotal(String studentId) —— 从 scoreMap 取该学生成绩列表求总分
+        public double getStudentTotal(String studentId){
+            List<Score> list = scoreMap.get(studentId);
+            double sum=0;
+            for (Score s:list){
+                sum+=s.getScore();
+            }
+            return sum;
+        }
+
+    // TODO: getStudentAvg(String studentId)   —— 求平均分
+    public double getStudentAvg(String studentId){
+       return getStudentTotal(studentId)/scoreMap.values().size();
+    }
+    // TODO: countPass() —— 统计及格(>= 60)人数 / 及格率(用运算符计算百分比,保留两位小数)
+    public double countPass(){
+        Set<String> studentIds = scoreMap.keySet(); //拿到所有学生id
+        int count=0;    //及格人数
+        for (String studentId:studentIds){
+            double studentAvg = getStudentAvg(studentId);   //得到每个学生的所有成绩的平均分
+            if (studentAvg>=60){    //大于等于60
+                count++; //算一个及格人数
+            }
+        }
+        return count/studentIds.size();
+    }
+}

+ 87 - 0
授课代码/c260806/src/homework0804/manager/StudentManager.java

@@ -0,0 +1,87 @@
+package homework0804.manager;
+
+import homework0804.entity.Student;
+import homework0804.exception.DuplicateStudentException;
+import homework0804.exception.StudentNotFoundException;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title StudentManager
+ * @description
+ * @create 2026/8/6
+ */
+public class StudentManager {
+    private ArrayList<Student> students;   // 泛型集合
+
+    public StudentManager() {
+        this.students = new ArrayList<>();
+    }
+
+    // TODO: addStudent(Student s) —— 添加学生
+    //   要求:先判断学号是否已存在(遍历 students 用 equals / id 比对)
+    //         已存在 → 抛 DuplicateStudentException("学号已存在:S001")
+    //         不存在 → students.add(s),输出"添加成功"
+    public void addStudent(Student s) throws DuplicateStudentException {
+        //foreach 遍历方式...
+        for (Student s1: students){
+            if (s1.getId().equals(s.getId())) {
+                throw new DuplicateStudentException("学号已存在:"+s.getId());
+            }
+        }
+        students.add(s);
+    }
+
+
+
+    // TODO: removeById(String id) —— 按学号删除学生
+    //   要求:找到并删除;找不到 → 抛 StudentNotFoundException("学生不存在:S001")
+    public void removeById(String id) throws StudentNotFoundException {
+        for (Student s1: students){
+            if (s1.getId().equals(id)) {
+                students.remove(id);
+                return; //截停...方法停止
+            }
+        }
+        throw new StudentNotFoundException("学生不存在:"+id);
+    }
+
+    // TODO: findById(String id) —— 按学号查找,返回 Student;找不到返回 null
+    //   要求:必须使用迭代器 Iterator 遍历(复习迭代器知识点)
+    public Student findById(String id){
+        Iterator<Student> iterator = students.iterator();
+        while (iterator.hasNext()){
+            Student student = iterator.next();
+            if (student.getId().equals(id)){
+                return student;
+            }
+        }
+        return null;
+    }
+
+
+    // TODO: printAll() —— 遍历输出所有学生(foreach 或迭代器)
+    public void printAll(){
+        students.stream().forEach(System.out::println);
+    }
+
+
+    // TODO: getStudents() —— 返回学生集合(供排序模块使用)
+    public List<Student> getStudents(){
+        return this.students;
+    }
+
+    // TODO: updateStudent(String id, String newName, int newAge) —— 遍历输出所有学生(foreach 或迭代器)
+    public void  updateStudent(String id, String newName, int newAge){
+        //查询到学生
+        Student student = findById(id);
+        //改名
+        student.setName(newName);
+        //改年龄
+        student.setAge(newAge);
+    }
+}

+ 80 - 0
授课代码/c260806/src/homework0804/util/RankUtil.java

@@ -0,0 +1,80 @@
+package homework0804.util;
+
+import homework0804.entity.Person;
+import homework0804.entity.Student;
+import homework0804.manager.StudentManager;
+
+import java.util.*;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title RankUtil
+ * @description
+ * @create 2026/8/6
+ */
+public class RankUtil {
+    /**
+     * 功能 1:成绩排行榜 —— TreeSet 自然排序
+     * 利用 Student 的 compareTo(平均分降序,相同按学号),
+     * 把学生添加进 TreeSet<Student>,迭代器遍历输出排行榜
+     */
+    public static void showRank(StudentManager sm) {
+        // TODO: 1) 从 sm.getStudents() 拿到 ArrayList<Student>
+        //       2) 创建 TreeSet<Student>,把所有学生 add 进去(自动按自然排序排好 + 去重)
+        //       3) 用 Iterator 遍历输出,格式:排名-学号-姓名-平均分
+        List<Student> studentList = sm.getStudents();
+        TreeSet<Student> treeSet=new TreeSet<>(studentList);
+        Iterator<Student> iterator = treeSet.iterator();
+        while (iterator.hasNext()){
+            System.out.println(iterator.next());
+        }
+    }
+
+    /**
+     * 功能 2:按姓名排序 —— Comparator 匿名内部类
+     * 使用 Collections.sort(list, new Comparator<Student>() {...}),
+     * 匿名内部类中 o1.getName().compareTo(o2.getName())
+     */
+    public static void sortByName(StudentManager sm) {
+        // TODO: 拷贝一份学生列表,Collections.sort + 匿名内部类 Comparator 按姓名升序
+        List<Student> studentList = sm.getStudents();
+        Collections.sort(studentList, new Comparator<Student>() {
+            @Override
+            public int compare(Student o1, Student o2) {
+                return o1.getName().compareTo(o2.getName());
+            }
+        });
+        //       遍历输出
+        sm.printAll();
+
+    }
+
+    /**
+     * 功能 3:按年龄排序 —— Comparator + Lambda
+     * 使用 Collections.sort(list, (s1, s2) -> Integer.compare(s1.getAge(), s2.getAge()));
+     */
+    public static void sortByAge(StudentManager sm) {
+        // TODO: 拷贝学生列表,用 Lambda 按年龄升序排序,遍历输出
+        List<Student> studentList = sm.getStudents();
+        Collections.sort(studentList, (s1, s2) -> Integer.compare(s1.getAge(), s2.getAge()));
+        sm.printAll();
+    }
+
+    /**
+     * 功能 4:按平均分降序(和自然排序对比)—— 用 Collections.sort + 匿名内部类
+     * 要求:不依赖 Student 的 compareTo,在 Comparator 中自己算平均分(Double.compare 降序)
+     */
+    public static void sortByAvgDesc(StudentManager sm) {
+        // TODO: Double.compare(o2.getAvgScore(), o1.getAvgScore()) 实现降序
+        List<Student> studentList = sm.getStudents();
+        Collections.sort(studentList, new Comparator<Student>() {
+            @Override
+            public int compare(Student o1, Student o2) {
+                return Double.compare(o2.getAvgScore(), o1.getAvgScore());
+            }
+        });
+        sm.printAll();
+
+    }
+}

+ 96 - 0
课后作业/2026年8月6日-作业.md

@@ -0,0 +1,96 @@
+# 2026年8月6日 课后作业 — File 文件操作 + IO 流入门:文件复制器
+
+> **说明**:本次课程学习了 **文件操作类 File**(创建 / 删除 / 判断 / 获取)和 **IO 流入门**(字节流:`FileOutputStream` 输出、`FileInputStream` 输入、文件复制)。本作业只出 **1 道题——文件复制器**,要求用 `FileInputStream` + `FileOutputStream` 配合 `byte[]` 缓冲区完成文件的完整复制,并统计耗时。
+>
+> 这道题覆盖:**字节输入流读取(read / read(byte[]) / -1 结束)**、**字节输出流写入(write / write(byte[],0,len))**、**缓冲区思想**、**流的关闭**、**IOException 处理**、**System.currentTimeMillis() 耗时统计** 等核心知识点。
+
+---
+
+## 📌 作业总体要求
+
+### 一、命名要求
+
+| 项目 | 要求 |
+|------|------|
+| **类名** | `Demo_01_你的姓名`(如 `Demo_01_张三`) |
+| **文件头注释** | 每个 Java 文件头部需包含 `@author`、`@title`、`@create` 文档注释 |
+
+### 二、提交方式与截止时间
+
+| 项目 | 要求 |
+|------|------|
+| **提交方式** | 将源码文件 + 运行结果截图提交至班级指定平台 |
+| **截止时间** | 下次上课前 |
+
+---
+
+## 一、作业题目(必做 · 共 1 题)
+
+### 题目 1:文件复制器 FileCopy(⭐⭐⭐ 必做)
+
+**知识点**:`FileInputStream`(字节输入流读取)、`FileOutputStream`(字节输出流写入)、`read(byte[])` / `write(byte[], 0, len)`、缓冲区 `byte[4096]`、`-1` 文件末尾判断、`close()` 关闭流、`IOException`、`System.currentTimeMillis()` 耗时统计
+
+**场景描述**:文件复制 = **输入流读 + 输出流写**:用 `FileInputStream` 把源文件的数据读到 `byte[]` 缓冲区,再用 `FileOutputStream` 把缓冲区内容写入目标文件,循环往复直到读完(`read` 返回 `-1`)。请实现一个**文件复制器**,把任意文件(如视频 .mp4)完整复制一份,并输出复制耗时。
+
+**题目要求**:
+
+```java
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * @author 你的姓名
+ * @title 题目1:文件复制器
+ * @create 2026/8/6
+ */
+public class Demo_01_姓名 {
+    public static void main(String[] args) throws IOException {
+        String src = "E:/授课资料/示例.mp4";    // 源文件路径(请替换成你本机的文件)
+        String desc = "D:/示例_副本.mp4";       // 目标文件路径
+
+        long startTime = System.currentTimeMillis();   // 复制开始时刻(毫秒)
+
+        // TODO 1: 创建输入流对象(FileInputStream 读源文件)和输出流对象(FileOutputStream 写目标文件)
+
+        // TODO 2: 创建 byte[4096] 缓冲区;
+        //         用 while 循环调用 fis.read(bytes),返回值是本次读到的有效字节长度,
+        //         当返回 -1 表示读完结束循环;否则用 fos.write(bytes, 0, length) 写入本次有效字节
+
+        // TODO 3: 先关闭输出流,再关闭输入流
+
+        long endTime = System.currentTimeMillis();     // 复制结束时刻
+        System.out.println("复制完成!耗时:" + (endTime - startTime) + " 毫秒");
+    }
+}
+```
+
+**预期输出示例**:
+
+```
+复制完成!耗时:312 毫秒
+(目标路径下生成与源文件内容完全一致的副本文件)
+```
+
+**要求说明**:
+1. 复制前后对比源文件与目标文件的大小,必须**完全一致**(可用文件属性查看)
+2. 必须使用**缓冲区循环读取**(`while ((length = fis.read(bytes)) != -1)`),**不允许**用 `read()` 逐字节复制
+3. `write` 必须使用 `write(bytes, 0, length)`,**只写入本次读到的有效字节数**
+
+**💡 提示**:
+- `fis.read(bytes)` 返回的是**本次读取的有效字节长度**,可能小于缓冲区大小(如最后一段数据),所以写入时务必用 `0, length` 截断,否则会把缓冲区残留数据一起写入导致文件变大
+- 关闭顺序:先 `fos.close()` 再 `fis.close()`
+- 进阶思考:把 `byte[4096]` 改成 `byte[1024]` / `byte[8192]`,复制耗时有何变化?(缓冲区越大单次读写越高效,但过大会浪费内存)
+
+**🎯 挑战加分(选做)**:
+1. 用 `try-catch-finally` 保证无论是否发生异常都能关闭流(`finally` 中 `close()`)
+2. 用 `Scanner` 让用户从键盘输入源文件路径和目标文件路径
+3. 对比缓冲区大小 `1024` / `4096` / `8192` 三种情况下的复制耗时并输出
+
+---
+
+## 四、提交要求
+
+- **类名**:`Demo_01_姓名`(如 `Demo_01_张三`)
+- **文件头注释**:每个 Java 文件头部必须包含 `@author`(你的姓名)、`@title`(题目名称)、`@create 2026/8/6`
+- 提交内容:源码文件 + 运行结果截图

+ 1091 - 0
课堂笔记/01-JavaSE基础-笔记/20260806-笔记.md

@@ -0,0 +1,1091 @@
+# 20260806 课堂笔记 — 文件操作类 File(java.io.File)+ IO 流入门(字节流 + 文件字节输出流 FileOutputStream)+ JavaSE 综合大作业(20260804 课后作业)解析:学生信息管理系统(Person 继承体系 + 接口 + 自定义异常 + 集合 + 比较器排序 + Lambda/Stream 选做挑战)
+
+- **日期**:2026-08-06
+- **项目**:`c260806`
+- **包路径**:`course` / `homework0804`(`entity` / `exception` / `manager` / `main` / `util` 分层)
+- **作者**:WanJL
+
+---
+
+## 目录
+
+1. [文件操作类 File:java.io.File 的构造 / 创建 / 删除 / 判断 / 获取](#1-文件操作类-filejavaiofile-的构造--创建--删除--判断--获取)
+2. [IO 流入门:字节流(FileOutputStream 输出 / FileInputStream 输入 / 文件复制)](#2-io-流入门字节流fileoutputstream-输出--fileinputstream-输入--文件复制)
+3. [JavaSE 综合大作业(20260804 课后作业)解析:学生信息管理系统](#3-javase-综合大作业20260804-课后作业解析学生信息管理系统)
+   - [3.1 作业目标与项目分层结构](#31-作业目标与项目分层结构)
+   - [3.2 实体层 entity:抽象类 Person 与继承体系](#32-实体层-entity抽象类-person-与继承体系)
+   - [3.3 实体层 entity:接口 Studyable / Teachable 与多接口实现](#33-实体层-entity接口-studyable--teachable-与多接口实现)
+   - [3.4 实体层 entity:对象关联(Teacher → Course)与 toString 重写](#34-实体层-entity对象关联teacher--course与-tostring-重写)
+   - [3.5 实体层 entity:Score 成绩类 —— setter 校验 + 自定义异常 + equals/hashCode](#35-实体层-entityscore-成绩类--setter-校验--自定义异常--equalshashcode)
+   - [3.6 异常层 exception:四个自定义异常类的统一写法](#36-异常层-exception四个自定义异常类的统一写法)
+   - [3.7 管理层 manager:StudentManager(ArrayList + 迭代器 + 增删改查)](#37-管理层-managerstudentmanagerarraylist--迭代器--增删改查)
+   - [3.8 管理层 manager:CourseManager(Stream + forEach + 方法引用)](#38-管理层-managercoursemanagerstream--foreach--方法引用)
+   - [3.9 管理层 manager:ScoreManager(HashMap 按学号分组 + 及格率统计)](#39-管理层-managerscoremanagerhashmap-按学号分组--及格率统计)
+   - [3.10 工具层 util:RankUtil —— Comparable / Comparator / Lambda 三种排序方式](#310-工具层-utilrankutil--comparable--comparator--lambda-三种排序方式)
+   - [3.11 入口 main:StudentSystemApp —— Scanner + while + switch + 标签 break 菜单系统](#311-入口-mainstudentsystemapp--scanner--while--switch--标签-break-菜单系统)
+4. [知识点全景总结](#4-知识点全景总结)
+5. [随堂练习要点](#5-随堂练习要点)
+6. [拓展阅读](#6-拓展阅读)
+
+---
+
+## 1. 文件操作类 File:java.io.File 的构造 / 创建 / 删除 / 判断 / 获取
+
+### 概念 —— 文件与目录的抽象表示
+
+`java.io.File` 是**文件和目录路径名的抽象表示**:文件(File)和目录(文件夹)都可以封装成 File 对象。注意:**File 封装的不一定是真实存在的文件,只是一个路径名**——它可以是存在的,也可以是不存在的,需要将来通过这个路径进行具体操作,把文件路径内容转换为真实存在的。
+
+```java
+// 来源:course/Demo01.java(注释部分)
+/*
+    java.io.File
+    File类,是文件和目录路径名的抽象表示(类)
+    文件和目录(文件夹)可以通过File封装成对象
+    对于File来说,他的封装并不是一个真正存在的文件,只是一个路径名,他是可以存在,也可以不存在,
+    需要将来通过这个路径进行具体的操作,把文件路径内容转换为真实存在的。
+*/
+```
+
+> **要点**:File 类位于 `java.io` 包(IO 入门第一课),它**只描述路径、不做真正的读写**;真正读写文件内容需要后续的 IO 流(InputStream/OutputStream/Reader/Writer)。
+
+### 三种构造方法
+
+```java
+// 来源:course/Demo01.java(注释部分)
+public File(String pathname)                       // 通过给定的路径名字符串转换为抽象路径名
+public File(String parent, String child)           // 从父路径字符串和子路径字符串创建
+public File(File parent, String child)             // 从父抽象路径名(File对象)和子路径名字符串创建
+```
+
+### 路径分隔符 + 绝对路径 vs 相对路径
+
+| 概念 | 说明 |
+|------|------|
+| **路径分隔符** | Windows 是 `\`;Linux / UNIX / 安卓 / iOS 都是 `/` |
+| **绝对路径** | 完整路径,从盘符(C盘/D盘)或根目录 `/` 开始,如 `E:/abc/a.txt`(可理解为快递地址/经纬度) |
+| **相对路径** | 简化的路径,相对于某个位置(可理解为前后左右),如 `./` 表示当前项目的同一层级,`../` 表示当前项目的上一层级 |
+
+### 创建 / 删除 / 判断 / 获取 功能方法
+
+```java
+// 来源:course/Demo01.java(注释部分)
+// 创建文件及目录
+public boolean createNewFile()   // 该文件不存在时创建新文件,会抛异常 IOException
+public boolean mkdir()           // 创建新文件夹(单级)
+public boolean mkdirs()          // 创建新文件夹(多级),父目录不存在则一并创建
+// 删除文件或目录
+public boolean delete()          // 删除文件或目录
+// 判断
+public boolean isDirectory()     // 判断是否是文件夹
+public boolean isFile()          // 判断是否是文件
+public boolean exists()          // 判断该路径是否真实存在
+// 获取功能
+public String getAbsolutePath()  // 获取绝对路径
+public String getPath()          // 获取路径
+public String getName()          // 获取文件名或目录名
+public File[] listFiles()        // 返回该路径目录下所有文件和目录组成的数组
+```
+
+### main 演示 —— 相对路径操作 + 六大获取/判断方法
+
+```java
+// 来源:course/Demo01.java
+public static void main(String[] args) throws IOException {
+    File file = new File("./");          // 相对路径:当前项目同层级
+    //file.createNewFile();              // 创建文件(会抛 IOException,已注释)
+    boolean b = file.mkdirs();           // 多级创建文件夹
+    //boolean d = file.delete();         // 删除(已注释)
+
+    System.out.println("是否创建成功:" + b);
+    System.out.println("绝对路径:" + file.getAbsolutePath());
+    System.out.println("相对路径:" + file.getPath());
+    System.out.println("是否是文件:" + file.isFile());
+    System.out.println("是否是文件夹:" + file.isDirectory());
+    System.out.println("是否存在:" + file.exists());
+    System.out.println("文件名:" + file.getName());
+    System.out.println("里面有哪些文件:" + Arrays.toString(file.listFiles()));
+}
+```
+
+> **要点**:
+> - `createNewFile()` 声明抛 **`IOException`**,所以 main 方法用 `throws IOException` 声明(编译期异常)
+> - `mkdirs()` vs `mkdir()`:前者**多级递归创建**父目录,更常用
+> - `listFiles()` 返回 **`File[]` 数组**,用 `Arrays.toString()` 打印可看到目录下所有文件/子目录
+> - `./` 相对路径指向**当前项目目录**,是后续文件读写的起点概念
+
+---
+
+## 2. IO 流入门:字节流(FileOutputStream 输出 / FileInputStream 输入 / 文件复制)
+
+### 概念 —— 什么是 IO 流
+
+IO 是**输入输出**(Input/Output);流(Stream)是一种**抽象概念**,是对数据传输的总称——数据在设备之间的传输称为流,流的本质就是**数据传输**。IO 流用来处理设备之间的数据传输问题,常见应用场景:**文件复制、文件上传、文件下载**。
+
+```java
+// 来源:course/Demo02.java(注释部分)
+/*
+    IO流
+    IO:输入输出(Input/Output)
+    流:Stream是一种抽象概念,是对数据传输的总称,也就是说数据在设备直接的传输称为流,流的本质就是数据传输
+    IO流就是用来处理设备之间的数据传输问题的,常见应用场景:文件赋值、文件上传、文件下载。
+*/
+```
+
+### IO 流的分类(两个维度)
+
+| 分类维度 | 类别 | 说明 |
+|----------|------|------|
+| **按数据流向**(相对于内存) | 输入流 | 向内存输入数据(读数据) |
+| | 输出流 | 从内存往外输出数据(写数据) |
+| **按数据类型** | 字节流 | 传输的每个数据都是 byte 类型(字节输入流 / 字节输出流) |
+| | 字符流 | 传输的每个数据都是 char 类型(字符输入流 / 字符输出流) |
+
+### IO 流的使用场景推荐
+
+- **纯文本文件(.txt)**:建议使用**字符流**,因为 1 个字符等于 2 个字节
+- **除纯文本之外的任何文件**(Word / Excel / PPT / md / png / mp3 / mp4 / rmvb 等):建议**字节流**
+- **不确定文件类型**:用**字节流**
+
+```java
+// 来源:course/Demo02.java(注释部分)
+/*
+    如果是纯文本文件(.txt),建议使用字符流,因为1个字符等于2个字节
+    除了纯文本文件之外的任何文件包括文本、图片、视频、音频、等等任何文件,都建议是字节流。
+    如果不确定文件类型,就是有字节流
+*/
+```
+
+### InputStream 与 OutputStream —— 字节流两大抽象父类
+
+- `OutputStream`:抽象类,表示**所有字节输出流**类的父类
+- `InputStream`:抽象类,表示**所有字节输入流**类的父类
+- 子类命名特点:**子类的名称都以父类类名作为子类的后缀**(如文件字节输入流 `FileInputStream`、文件字节输出流 `FileOutputStream`)
+
+```java
+// 来源:course/Demo02.java(注释部分)
+/*
+    OutputStream:这是一个抽象类,表示所有字节输出流类的父类
+    InputStream:这是一个抽象类,表示所有字节输入流类的父类
+    文件字节输入流:FileInputStream
+    文件字节输出流:FileOutputStream
+*/
+```
+
+### 使用字节输出流的三个步骤
+
+1. 创建字节输出流对象
+2. 调用字节输出流的写数据方法
+3. 释放资源,关闭流
+
+```java
+// 来源:course/Demo02.java(注释部分)
+/*
+    使用字节输出流的步骤:
+        1、创建字节输出流对象
+        2、调用字节输出流的写数据的方法
+        3、释放资源,关闭流
+*/
+```
+
+### FileOutputStream 的四种构造方法
+
+```java
+// 来源:course/FileOutputStreamTest.java(注释部分)
+FileOutputStream(File file)                   // 创建输出流,参数为 File 对象
+FileOutputStream(String name)                 // 创建输出流,参数为字符串路径
+FileOutputStream(File file, boolean append)   // append 为 true 表示追加内容(否则覆盖)
+FileOutputStream(String name, boolean append) // 字符串路径 + 追加开关
+```
+
+### write() 三种写数据方法
+
+```java
+// 来源:course/FileOutputStreamTest.java(注释部分)
+write(int b)                 // 写一个字节
+write(byte[] b)              // 写一个字节数组的内容
+write(byte[] b, int off, int len)   // 写字节数组的内容,从 off 开始,长度为 len
+```
+
+### main 演示 —— 三步走写入文件
+
+```java
+// 来源:course/FileOutputStreamTest.java
+public static void main(String[] args) throws IOException {
+    // 使用字节输出流的步骤:
+    // 1、创建字节输出流对象(append=true 表示追加写入)
+    FileOutputStream fos = new FileOutputStream("E:/我的文件.txt", true);
+
+    // 2、调用字节输出流的写数据方法
+    fos.write(97);            // 写一个字节(ASCII 97 = 'a')
+    fos.write(98);            // 'b'
+    fos.write(99);            // 'c'
+
+    byte[] arr = {65, 66, 67, 68, 69, 70, 71, 72};  // 对应 "ABCDEFGH"
+    fos.write(arr);                                 // 写整个字节数组
+    fos.write(arr, 3, 3);                           // 从下标 3 开始写 3 个:'D' 'E' 'F'
+    fos.write("\r\n".getBytes());                   // 写入换行(Windows 是 \r\n)
+
+    String s = """
+            两只黄鹂鸣翠柳,
+            一行白鹭上青天,
+            窗含西岭千秋雪,
+            门泊自挂东南枝。
+            """;
+    byte[] bytes = s.getBytes();    // 字符串 → 字节数组
+    fos.write(bytes);
+
+    // 3、释放资源,关闭流
+    fos.close();
+}
+```
+
+### 换行符对比
+
+| 系统 | 换行符 |
+|------|--------|
+| Linux | `\n` |
+| Windows | `\r\n` |
+| macOS | `\r` |
+
+> **要点**:
+> - `FileOutputStream` 是**字节输出流**,写入的是 byte 数据;字符串需先 `getBytes()` 转成字节数组
+> - `append=true` 是**追加写**(不会覆盖原内容);默认不带 append 参数是**覆盖写**
+> - 流用完之后**必须 `close()` 释放资源**,否则文件句柄不释放、数据可能没刷入磁盘
+> - `write(int b)` 传入的整数实际写的是低 8 位字节(ASCII 码),97→'a'、65→'A'
+> - main 方法声明 `throws IOException`,因为构造和 write 都可能抛编译期异常
+
+### 使用字节输入流的步骤(与输出流对称)
+
+```java
+// 来源:course/Demo02.java(注释部分)
+/*
+    使用字节输入流的步骤:
+        1、创建字节输入流对象
+        2、调用字节输入流的读数据的方法
+        3、释放资源,关闭流
+*/
+```
+
+### FileInputStream —— 文件字节输入流
+
+**读取方法**(与 write 三种写法一一对应):
+- `read()`:读取一个字节
+- `read(byte[] b)`:一次读取数据存入到字节数组 b 中
+- `read(byte[] b, int off, int len)`:读入字节数组,从 off 开始,长度为 len
+
+> **关键返回值**:`read()` 返回 **-1 表示读到文件末尾(EOF)**;`read(byte[] b)` 返回**本次读取到的有效字节长度**。
+
+```java
+// 来源:course/FileInputStreamTest.java
+public static void main(String[] args) {
+    // 使用字节输入流的步骤:
+    FileInputStream fis = null;
+    try {
+        // 1、创建字节输入流对象
+        fis = new FileInputStream("E:/我的文件.txt");
+        // 2、调用字节输入流的读数据方法
+        byte[] bytes = new byte[1024];   // 1KB 缓冲区
+        System.out.println(fis.read());  // 读取一个字节并输出
+
+        int length;   // 定义每一次读取的字节数量
+        // fis.read(bytes) 的返回值是本次读取到的有效字节长度;-1 表示读完
+        while ((length = fis.read(bytes)) != -1) {
+            System.out.println(new String(bytes, 0, length, Charset.forName("UTF-8")));
+        }
+    } catch (IOException e) {
+        e.printStackTrace();
+    } finally {   // finally 中写 close() 关闭流
+        try {
+            // 3、释放资源,关闭流
+            fis.close();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
+```
+
+> **要点**:
+> - **循环读**是经典写法:`while ((length = fis.read(bytes)) != -1)` —— 边读边转,`-1` 是读取结束的哨兵值
+> - 字节数组转字符串时指定编码:`new String(bytes, 0, length, Charset.forName("UTF-8"))`(0 起、读 length 个有效字节,避免把缓冲区的脏数据也转进去)
+> - 用 **try-catch-finally 关闭资源**:`finally` 保证无论是否发生异常都会执行 `close()`
+> - `read(byte[])` 每次读一批(缓冲区思想),比逐字节 `read()` 效率高得多
+
+### 文件复制 FileCopy —— 输入流 + 输出流配合
+
+**文件复制 = 输入流读 + 输出流写**:用 `FileInputStream` 从源文件读到缓冲区,再用 `FileOutputStream` 把缓冲区写入目标文件,循环直到读完。
+
+```java
+// 来源:course/FileCopy.java
+public static void copy(String src, String desc) throws IOException {
+    // 1、创建输入流对象
+    FileInputStream fis = new FileInputStream(src);
+    // 2、创建输出流对象
+    FileOutputStream fos = new FileOutputStream(desc);
+
+    // 3、创建一个字节数组作为缓冲区(每次最多复制 4096 字节)
+    byte[] bytes = new byte[4096];
+    int length;   // 每次读取并复制的字节数量
+    while ((length = fis.read(bytes)) != -1) {   // 循环读取,-1 表示读完
+        fos.write(bytes, 0, length);            // 循环写入本次有效字节
+    }
+
+    // 释放资源
+    fos.close();
+    fis.close();
+}
+```
+
+```java
+// 来源:course/FileCopy.java(main 调用 + 耗时统计)
+public static void main(String[] args) throws IOException {
+    String src = "E:\\...\\14_git下载安装.mp4";   // 源文件(视频)
+    String desc = "D:\\abc.mp4";                  // 目标文件
+    long startTime = System.currentTimeMillis();  // 复制前时刻(1970 至今的毫秒数)
+    copy(src, desc);
+    long endTime = System.currentTimeMillis();    // 复制后时刻
+    System.out.println(endTime - startTime);      // 输出复制耗时(毫秒)
+}
+```
+
+> **要点**:
+> - **缓冲区思想**:`byte[]` 一次读/写一批,而不是一个字节一个字节操作,大幅提升效率(视频、图片等大文件复制必备)
+> - `read(bytes)` 返回有效字节数,`write(bytes, 0, length)` 只写入本次读到的有效部分,**防止把缓冲区残留数据也写进去**
+> - 关闭顺序:先关输出流(fos),再关输入流(fis)
+> - `System.currentTimeMillis()` 返回从 1970-01-01 起的毫秒数,两时刻相减即可统计复制耗时
+
+---
+
+## 3. JavaSE 综合大作业(20260804 课后作业)解析:学生信息管理系统
+
+### 3.1 作业目标与项目分层结构
+
+### 作业目标
+
+这是 **20260804 课后作业 —— JavaSE 综合大作业**,把整个 JavaSE 阶段学过的核心知识点**综合**到一个小型"学生信息管理系统"中,核心功能:
+
+- **学生管理**:添加 / 删除 / 查找 / 查看所有学生(学号唯一,重复抛异常)
+- **课程管理**:课程编号唯一(重复不允许添加)
+- **成绩管理**:录入成绩(0~100 校验)、按学号求总分 / 平均分、及格率统计
+- **成绩排名**:TreeSet 自然排序、Comparator 按姓名 / 按年龄(Lambda)/ 按平均分降序
+- **高级查询**:挑战 · 选做(Stream 流)
+
+### 分层结构
+
+```
+src/homework0804/
+├── entity/      实体类:Person、Student、Teacher、Course、Score + 接口 Studyable/Teachable
+├── exception/   自定义异常:Course/DuplicateStudent/ScoreOutOfBounds/StudentNotFound + Exception
+├── manager/     管理类:StudentManager、CourseManager、ScoreManager(业务逻辑)
+├── main/        入口类:StudentSystemApp(main 方法 + 菜单)
+└── util/        工具类:RankUtil(排序)
+```
+
+```java
+// 来源:homework0804/main/StudentSystemApp.java(包导入)
+package homework0804.main;
+import homework0804.entity.*;
+import homework0804.exception.*;
+import homework0804.manager.*;
+import homework0804.util.*;
+```
+
+> **设计思想**:按职责把代码拆成 `entity`(数据)/ `exception`(错误)/ `manager`(业务)/ `util`(工具)/ `main`(入口)五个包,是后续分层架构(如三层架构 Controller/Service/DAO)的雏形。
+
+---
+
+### 3.2 实体层 entity:抽象类 Person 与继承体系
+
+### 抽象类 Person —— 抽取共性
+
+把"学生 / 教师"的**公共属性(姓名、年龄)**抽到父类 `Person`,并声明**抽象方法 `getRole()`** 强制子类返回角色描述:
+
+```java
+// 来源:homework0804/entity/Person.java
+public abstract class Person {
+    private String name;   // 姓名
+    private int age;       // 年龄
+
+    public Person(String name, int age) {
+        this.name = name;
+        this.age = age;
+    }
+    // getter / setter 省略...
+    // 抽象方法 —— 返回角色描述("学生" / "教师")
+    public abstract String getRole();
+
+    @Override
+    public String toString() {
+        return "姓名:" + name + ",年龄:" + age;
+    }
+}
+```
+
+### Student —— 继承 + 接口多实现
+
+`Student` 继承 `Person`,同时实现 `Comparable<Student>` 和 `Studyable` 两个接口,**「继承 + 多接口」**是 Java 多继承能力的替代方案:
+
+```java
+// 来源:homework0804/entity/Student.java
+public class Student extends Person implements Comparable<Student>, Studyable {
+    private String id;                // 学号,如 S001
+    private String clazz;             // 班级,如 "Java2301"
+    private List<Score> scores;       // 该学生的成绩列表(对象关联)
+
+    public Student(String name, int age, String id, String clazz) {
+        super(name, age);             // 调用父类构造方法(继承知识点)
+        this.id = id;
+        this.clazz = clazz;
+        this.scores = new ArrayList<>();
+    }
+}
+```
+
+### 子类必须实现抽象方法 getRole()
+
+```java
+// 来源:homework0804/entity/Student.java
+@Override
+public String getRole() {
+    return "学生";
+}
+```
+
+> **要点**:抽象类不能被实例化,子类必须重写所有抽象方法(否则子类也必须是抽象类)。
+
+---
+
+### 3.3 实体层 entity:接口 Studyable / Teachable 与多接口实现
+
+### 接口定义 —— 规范"能做什么"
+
+```java
+// 来源:homework0804/entity/Studyable.java
+public interface Studyable {
+    void study();   // 学习
+}
+```
+
+```java
+// 来源:homework0804/entity/Teachable.java
+public interface Teachable {
+    void teach();   // 授课
+}
+```
+
+### 接口实现
+
+`Student` 实现 `Studyable`,`Teacher` 实现 `Teachable`:
+
+```java
+// 来源:homework0804/entity/Student.java
+@Override
+public void study() {
+    System.out.println(this.getName() + " 同学正在努力学习...");
+}
+```
+
+```java
+// 来源:homework0804/entity/Teacher.java
+@Override
+public void teach() {
+    System.out.println(this.getName() + "老师正在讲授《" + this.course.getCourseName() + "》...");
+}
+```
+
+> **要点**:接口方法默认 `public abstract`,实现类必须 `public` 重写;接口是"契约",体现**面向接口编程**思想。
+
+---
+
+### 3.4 实体层 entity:对象关联(Teacher → Course)与 toString 重写
+
+### Teacher 持有 Course —— 对象作为成员变量
+
+一个教师"教一门课",所以 `Teacher` 里直接持有一个 `Course` 对象,这就是**对象关联 / 对象组合**:
+
+```java
+// 来源:homework0804/entity/Teacher.java
+public class Teacher extends Person implements Teachable {
+    private String id;        // 工号,如 T001
+    private Course course;    // 教授的课程(对象关联)
+
+    public Teacher(String name, int age, String id, Course course) {
+        super(name, age);
+        this.id = id;
+        this.course = course;
+    }
+}
+```
+
+### Course —— 最普通的 Bean 类
+
+`Course` 只含属性 + 构造方法 + getter/setter + toString,是标准的 **JavaBean**:
+
+```java
+// 来源:homework0804/entity/Course.java
+public class Course {
+    private String courseId;    // 课程编号,如 C001
+    private String courseName;  // 课程名称,如 "JavaSE 基础"
+    private int credit;         // 学分
+
+    public Course(String courseId, String courseName, int credit) {
+        this.courseId = courseId;
+        this.courseName = courseName;
+        this.credit = credit;
+    }
+    // getter / setter 省略...
+
+    @Override
+    public String toString() {
+        return courseId + "-" + courseName + "-" + credit;
+    }
+}
+```
+
+> **要点**:`toString()` 用 `-` 拼接,输出如 `C001-JavaSE 基础-3`,便于菜单展示。
+
+### 学生与成绩 —— 一对多的对象关联
+
+`Student` 中持有一个 `List<Score>`,体现**一对多关系**,并提供操作成绩的方法:
+
+```java
+// 来源:homework0804/entity/Student.java
+public void addScore(Score s) {
+    scores.add(s);
+}
+
+// 迭代器遍历累加总分(scores 为空返回 0)
+public double getTotalScore() {
+    Iterator<Score> iterator = scores.iterator();
+    double total = 0;
+    while (iterator.hasNext()) {
+        total += iterator.next().getScore();
+    }
+    return total;
+}
+
+public double getAvgScore() {
+    return getTotalScore() / getScoreCount();
+}
+
+public int getScoreCount() {
+    return scores.size();
+}
+```
+
+> **要点**:总分用 **Iterator 迭代器**遍历,是对 `hasNext()` / `next()` 的复习。
+
+---
+
+### 3.5 实体层 entity:Score 成绩类 —— setter 校验 + 自定义异常 + equals/hashCode
+
+### setter 中做业务校验并抛自定义异常
+
+成绩必须在 **0~100** 之间,在构造方法中调用 `setScore()` 完成校验:
+
+```java
+// 来源:homework0804/entity/Score.java
+public class Score {
+    private String studentId;   // 学号
+    private String courseId;    // 课程编号
+    private double score;       // 成绩(0~100)
+
+    public Score(String studentId, String courseId, double score) throws ScoreOutOfBoundsException {
+        this.studentId = studentId;
+        this.courseId = courseId;
+        setScore(score);        // 在 setter 中校验并抛出异常
+    }
+
+    public void setScore(double score) throws ScoreOutOfBoundsException {
+        if (score < 0 || score > 100)
+            throw new ScoreOutOfBoundsException("成绩必须在 0~100 之间,当前值:" + score);
+        this.score = score;
+    }
+}
+```
+
+> **要点**:**"构造校验" 设计模式** —— 通过构造方法强制对象创建时就满足数据合法,非法数据根本无法生成对象。
+
+### equals / hashCode —— 以 studentId + courseId 判定同一成绩
+
+同一学生同一课程视为同一条成绩记录,重写 equals 和 hashCode(配合去重 / 集合查找):
+
+```java
+// 来源:homework0804/entity/Score.java
+@Override
+public boolean equals(Object o) {
+    if (!(o instanceof Score score)) return false;      // instanceof 模式匹配(JDK16+)
+    return Objects.equals(studentId, score.studentId)
+            && Objects.equals(courseId, score.courseId);
+}
+
+@Override
+public int hashCode() {
+    return Objects.hash(studentId, courseId);
+}
+```
+
+> **要点**:重写 equals 后**必须**同步重写 hashCode(保证 hashCode 一致性的约定);`Objects.equals` / `Objects.hash` 是空安全的工具方法;`instanceof` 模式匹配是 JDK16+ 语法。
+
+### Student 的 equals/hashCode —— 以学号判定同一人
+
+```java
+// 来源:homework0804/entity/Student.java
+@Override
+public boolean equals(Object o) {
+    if (!(o instanceof Student student)) return false;
+    return Objects.equals(id, student.id);
+}
+
+@Override
+public int hashCode() {
+    return Objects.hashCode(id);
+}
+```
+
+---
+
+### 3.6 异常层 exception:四个自定义异常类的统一写法
+
+自定义异常**继承 `Exception`**(编译期异常),提供无参构造 + 带 message 构造,写法完全统一:
+
+```java
+// 来源:homework0804/exception/StudentNotFoundException.java
+public class StudentNotFoundException extends Exception {
+    public StudentNotFoundException() { }
+    public StudentNotFoundException(String message) { super(message); }
+}
+```
+
+四个异常一览:
+
+| 异常类 | 抛出场景 |
+|--------|----------|
+| `DuplicateStudentException` | 添加学生时学号已存在 |
+| `StudentNotFoundException` | 删除 / 查找学生时学号不存在 |
+| `CourseNotFoundException` | 查找 / 删除课程时编号不存在 |
+| `ScoreOutOfBoundsException` | 成绩不在 0~100 范围内 |
+
+> **要点**:继承 `Exception` 属于**编译期(受检)异常**,要求调用方必须 try-catch 或 throws;业务异常用自定义异常携带中文提示信息,`e.getMessage()` 可直接面向用户展示。
+
+---
+
+### 3.7 管理层 manager:StudentManager(ArrayList + 迭代器 + 增删改查)
+
+### addStudent —— foreach 判重 + 抛自定义异常
+
+```java
+// 来源:homework0804/manager/StudentManager.java
+public class StudentManager {
+    private ArrayList<Student> students;   // 泛型集合
+
+    public void addStudent(Student s) throws DuplicateStudentException {
+        // foreach 遍历判重
+        for (Student s1 : students) {
+            if (s1.getId().equals(s.getId())) {
+                throw new DuplicateStudentException("学号已存在:" + s.getId());
+            }
+        }
+        students.add(s);
+    }
+}
+```
+
+> **要点**:业务上"学号唯一"的保证方式 —— 先遍历判断(这里用 `getId().equals()` 比对,可改用 `s1.equals(s)` 走重写的 equals)。
+
+### findById —— 必须用 Iterator 迭代器(复习迭代器知识点)
+
+```java
+// 来源:homework0804/manager/StudentManager.java
+public Student findById(String id) {
+    Iterator<Student> iterator = students.iterator();
+    while (iterator.hasNext()) {
+        Student student = iterator.next();
+        if (student.getId().equals(id)) {
+            return student;             // 找到返回对象
+        }
+    }
+    return null;                        // 找不到返回 null
+}
+```
+
+### removeById —— 找到即删 + return 截停 + 抛异常
+
+```java
+// 来源:homework0804/manager/StudentManager.java
+public void removeById(String id) throws StudentNotFoundException {
+    for (Student s1 : students) {
+        if (s1.getId().equals(id)) {
+            students.remove(id);        // 这里用 remove(Object) 按元素删除
+            return;                     // 截停:方法立即结束
+        }
+    }
+    throw new StudentNotFoundException("学生不存在:" + id);
+}
+```
+
+> **说明**:`students.remove(id)` 传入的是 String,实际调用的是 `remove(Object)`,会调用 Student 的 `equals` 把"学号相等的学生"删掉(因为 equals 以学号判定),因此**重写 equals 在这里起了作用**。
+
+### updateStudent —— 查出来再改(setter 修改)
+
+```java
+// 来源:homework0804/manager/StudentManager.java
+public void updateStudent(String id, String newName, int newAge) {
+    Student student = findById(id);
+    student.setName(newName);
+    student.setAge(newAge);
+}
+```
+
+---
+
+### 3.8 管理层 manager:CourseManager(Stream + forEach + 方法引用)
+
+### addCourse —— 用"抛异常判断存在性"的反向逻辑
+
+```java
+// 来源:homework0804/manager/CourseManager.java
+public void addCourse(Course c) {
+    try {
+        findById(c.getCourseId());   // 找到 → 说明重复 → 不添加
+    } catch (CourseNotFoundException e) {
+        courses.add(c);              // 没找到 → 抛异常 → 才添加
+    }
+}
+
+public Course findById(String courseId) throws CourseNotFoundException {
+    for (Course c : courses) {
+        if (c.getCourseId().equals(courseId)) {
+            return c;
+        }
+    }
+    throw new CourseNotFoundException();
+}
+```
+
+> **要点**:这种"以异常控制流程"的写法虽然可行,但**不推荐**(异常开销大、流程不直观);更规范的做法是先判重再 add。
+
+### printAll —— Stream 流 + 方法引用(选做挑战)
+
+```java
+// 来源:homework0804/manager/CourseManager.java
+public void printAll() {
+    courses.stream().forEach(System.out::println);   // Stream + 方法引用
+}
+```
+
+> **要点**:`集合.stream()` 获取流 → `forEach` 终结方法遍历 → `System.out::println` 是**引用对象的实例方法**的方法引用形式(等价于 Lambda `c -> System.out.println(c)`)。
+
+---
+
+### 3.9 管理层 manager:ScoreManager(HashMap 按学号分组 + 及格率统计)
+
+### 双数据结构 —— 成绩台账 + 按学号分组 Map
+
+```java
+// 来源:homework0804/manager/ScoreManager.java
+public class ScoreManager {
+    private ArrayList<Score> scores;                    // 成绩台账(全量)
+    private HashMap<String, List<Score>> scoreMap;      // 学号 → 该学生的成绩列表(Map 知识点)
+}
+```
+
+### addScore —— 三处同步记录 + Map 分组 put 流程
+
+```java
+// 来源:homework0804/manager/ScoreManager.java
+public void addScore(Student stu, Course cou, double sc) throws ScoreOutOfBoundsException {
+    Score score = new Score(stu.getId(), cou.getCourseId(), sc);   // 越界抛异常
+    this.scores.add(score);      // ① 全局成绩台账
+    stu.addScore(score);         // ② 学生侧记录(对象关联)
+    // ③ 更新 scoreMap:按学号分组
+    if (scoreMap.containsKey(stu.getId())) {
+        List<Score> list = scoreMap.get(stu.getId());
+        list.add(score);
+    } else {
+        List<Score> scoreList = new ArrayList<>();
+        scoreMap.put(stu.getId(), scoreList);   // 先 put 一个新的 ArrayList
+        scoreList.add(score);                    // 再 add
+    }
+}
+```
+
+> **要点**:`HashMap<String, List<Score>>` 实现**「学号 → 成绩列表」的分组**;`containsKey` + `get` + `put` 是 Map 的经典三步操作。
+
+### 总分 / 平均分 / 及格率 —— Map 的 keySet 遍历 + 统计
+
+```java
+// 来源:homework0804/manager/ScoreManager.java
+public double getStudentTotal(String studentId) {
+    List<Score> list = scoreMap.get(studentId);
+    double sum = 0;
+    for (Score s : list) {
+        sum += s.getScore();
+    }
+    return sum;
+}
+
+public double getStudentAvg(String studentId) {
+    return getStudentTotal(studentId) / scoreMap.values().size();
+}
+
+// 统计及格(>= 60)率
+public double countPass() {
+    Set<String> studentIds = scoreMap.keySet();      // 拿到所有学生 id
+    int count = 0;
+    for (String studentId : studentIds) {
+        double studentAvg = getStudentAvg(studentId);
+        if (studentAvg >= 60) {
+            count++;
+        }
+    }
+    return count / studentIds.size();
+}
+```
+
+> **要点**:`keySet()` 拿全部键、`values()` 拿全部值、`size()` 取个数;及格率 = 及格人数 ÷ 总人数(结果建议再乘 100 保留两位小数展示)。
+
+---
+
+### 3.10 工具层 util:RankUtil —— Comparable / Comparator / Lambda 三种排序方式
+
+这一层是**排序知识点集大成**:一个工具类演示了四种排序写法。
+
+### ① TreeSet 自然排序 —— 依赖 Student 的 compareTo(平均分降序 + 学号二次比较)
+
+```java
+// 来源:homework0804/util/RankUtil.java
+public static void showRank(StudentManager sm) {
+    List<Student> studentList = sm.getStudents();
+    TreeSet<Student> treeSet = new TreeSet<>(studentList);   // 自动自然排序 + 去重
+    Iterator<Student> iterator = treeSet.iterator();
+    while (iterator.hasNext()) {
+        System.out.println(iterator.next());
+    }
+}
+```
+
+```java
+// 来源:homework0804/entity/Student.java(Comparable 实现)
+@Override
+public int compareTo(Student o) {
+    // 按平均分【降序】:交换参数位置即可实现降序
+    int result = Double.compare(o.getAvgScore(), this.getAvgScore());
+    // 平均分相同再按学号【升序】二次比较
+    return result == 0 ? this.id.compareTo(o.getId()) : result;
+}
+```
+
+> **要点**:`Comparable` 自然排序规则写在实体类内部;`Double.compare` 对 double 比较更安全(避免差值溢出);**降序技巧 = 交换 compareTo 两个参数的先后位置**;`TreeSet` 会**自动排序 + 按 compareTo 去重**。
+
+### ② Comparator 匿名内部类 —— 按姓名升序
+
+```java
+// 来源:homework0804/util/RankUtil.java
+public static void sortByName(StudentManager sm) {
+    List<Student> studentList = sm.getStudents();
+    Collections.sort(studentList, new Comparator<Student>() {
+        @Override
+        public int compare(Student o1, Student o2) {
+            return o1.getName().compareTo(o2.getName());
+        }
+    });
+    sm.printAll();
+}
+```
+
+> **要点**:`Comparator` 比较器排序规则写在**类外部**(作为排序参数传入),不改实体类即可临时指定排序规则;String 的 `compareTo` 按字典序比较。
+
+### ③ Comparator + Lambda —— 按年龄升序(Lambda 省略写法)
+
+```java
+// 来源:homework0804/util/RankUtil.java
+public static void sortByAge(StudentManager sm) {
+    List<Student> studentList = sm.getStudents();
+    Collections.sort(studentList, (s1, s2) -> Integer.compare(s1.getAge(), s2.getAge()));
+    sm.printAll();
+}
+```
+
+> **要点**:`Comparator` 是**函数式接口**(只有一个抽象方法 `compare`),可以用 Lambda 替代匿名内部类;`Integer.compare` 是 int 比较的安全写法。
+
+### ④ Comparator 匿名内部类 —— 不依赖 compareTo,自己按平均分降序
+
+```java
+// 来源:homework0804/util/RankUtil.java
+public static void sortByAvgDesc(StudentManager sm) {
+    List<Student> studentList = sm.getStudents();
+    Collections.sort(studentList, new Comparator<Student>() {
+        @Override
+        public int compare(Student o1, Student o2) {
+            return Double.compare(o2.getAvgScore(), o1.getAvgScore());   // 降序:交换参数
+        }
+    });
+    sm.printAll();
+}
+```
+
+> **要点**:与 ① 的区别 —— 排序规则不依赖 Student 内部实现的 compareTo,而是**在 Comparator 中自己计算**,体现了 Comparable 与 Comparator 的取舍。
+
+---
+
+### 3.11 入口 main:StudentSystemApp —— Scanner + while + switch + 标签 break 菜单系统
+
+### 初始化示例数据 + 代码块
+
+```java
+// 来源:homework0804/main/StudentSystemApp.java
+public static void main(String[] args) {
+    // 1. 创建各管理类对象
+    StudentManager sm = new StudentManager();
+    CourseManager cm = new CourseManager();
+    ScoreManager scm = new ScoreManager();
+
+    // 2. 初始化少量示例数据
+    Student s1 = new Student("张三", 18, "S001", "Java2301");
+    Student s2 = new Student("李四", 19, "S002", "Java2301");
+    Student s3 = new Student("王五", 20, "S003", "Java2302");
+    Course c1 = new Course("C001", "JavaSE 基础", 3);
+    Course c2 = new Course("C002", "MySQL 数据库", 2);
+    Course c3 = new Course("C003", "数据结构", 3);
+    Teacher teacher = new Teacher("王老师", 35, "T001", c1);
+
+    {   // 普通代码块:限定 try-catch 作用域
+        try {
+            sm.addStudent(s1);
+            sm.addStudent(s2);
+            sm.addStudent(s3);
+            cm.addCourse(c1);
+            cm.addCourse(c2);
+            cm.addCourse(c3);
+        } catch (DuplicateStudentException e) {
+            throw new RuntimeException(e);   // 编译期异常包装成运行时异常
+        }
+    }
+    ...
+}
+```
+
+> **要点**:用**普通代码块**(`{ }`)把初始化 try-catch 的局部变量作用域圈起来;`throw new RuntimeException(e)` 是把受检异常包装为运行时异常的一种方式(异常链)。
+
+### 主循环 + 主菜单 + 标签 break
+
+```java
+// 来源:homework0804/main/StudentSystemApp.java
+Scanner sc = new Scanner(System.in);
+
+// 主循环 + 主菜单(while + switch 箭头语法)
+one:
+while (true) {
+    System.out.println("===== 学生信息管理系统 =====");
+    System.out.println("1. 学生管理");
+    System.out.println("2. 课程管理");
+    System.out.println("3. 成绩管理");
+    System.out.println("4. 成绩排名");
+    System.out.println("5. 高级查询(挑战·选做)");
+    System.out.println("0. 退出");
+    switch (sc.next()) {
+        case "1" -> {
+            two:
+            while (true) {
+                // 二级菜单:学生管理
+                switch (sc.next()) {
+                    case "1" -> {   // 添加学生
+                        // Scanner 接收 name/age/id/clazz → new Student → sm.addStudent
+                        try {
+                            sm.addStudent(student);
+                        } catch (DuplicateStudentException e) {
+                            System.out.println(e.getMessage());   // 界面友好提示
+                        }
+                    }
+                    case "2" -> {   // 删除学生
+                        try {
+                            sm.removeById(id);
+                        } catch (StudentNotFoundException e) {
+                            System.out.println(e.getMessage());
+                        }
+                    }
+                    case "3" -> {   // 查找学生
+                        Student student = sm.findById(id);
+                        System.out.println(student);
+                    }
+                    case "4" -> {   // 查看所有学生
+                        for (Student student : sm.getStudents()) {
+                            System.out.println(student);
+                        }
+                    }
+                    case "0" -> {
+                        System.out.println("返回上一级");
+                        break two;      // 标签 break:跳出外层二级循环
+                    }
+                }
+            }
+        }
+        case "2" -> { }   // 课程管理(待完成)
+        case "3" -> { }   // 成绩管理(待完成)
+        case "4" -> { }   // 成绩排名(待完成)
+        case "5" -> { }   // 高级查询(待完成)
+        case "0" -> { }
+    }
+}
+```
+
+> **要点**:
+> - **switch 箭头语法**(JDK14+):`case "1" -> { ... }`,无需 break,代码更简洁
+> - **标签 break**:`two:` 给 while 循环命名,`break two` 可以直接跳出**外层二级循环**(普通 break 只能跳一层)
+> - 每个业务功能都用 **try-catch 处理自定义异常**并输出 `e.getMessage()`,让程序**鲁棒**(不因异常崩溃,而是提示用户)
+
+---
+
+## 4. 知识点全景总结
+
+本作业把 JavaSE 阶段核心知识点**串成一个系统**,一张表看清"知识 → 代码位置":
+
+| 知识点 | 具体体现 | 源码位置 |
+|--------|----------|----------|
+| **类与对象 / 封装** | 私有属性 + getter/setter + 构造方法 | entity 下所有实体类 |
+| **继承** | `Student extends Person` / `Teacher extends Person`,`super(name, age)` | entity/Student、Teacher、Person |
+| **抽象类** | `abstract class Person` + 抽象方法 `getRole()` | entity/Person |
+| **接口** | `Studyable` / `Teachable`,多接口实现 | entity/Student、Teacher |
+| **对象关联** | Teacher 持有 Course;Student 持有 `List<Score>` | entity/Teacher、Student |
+| **自定义异常** | 4 个异常类继承 Exception | exception/ |
+| **异常处理** | throws 声明 + try-catch 捕获 + getMessage 提示 | manager、main |
+| **集合框架** | `ArrayList`(学生/课程/成绩)、`HashMap`(学号分组)、`TreeSet`(排名) | manager、util |
+| **迭代器** | `Iterator` 遍历(hasNext/next) | Student、StudentManager |
+| **比较器排序** | Comparable(compareTo)+ Comparator(匿名内部类)+ Lambda + Double.compare | entity/Student、util/RankUtil |
+| **equals/hashCode** | Student 按学号、Score 按 学号+课程 重写 | entity/Student、Score |
+| **Stream 流(选做)** | `stream().forEach(System.out::println)` 方法引用 | CourseManager、StudentManager |
+| **流程控制** | Scanner 输入 + while 主循环 + switch 箭头语法 + 标签 break | main/StudentSystemApp |
+| **代码块** | 普通代码块 `{ }` 限定初始化 try-catch 作用域 | main/StudentSystemApp |
+| **IO 流(字节流)** | IO 概念、输入/输出与字节/字符流分类、OutputStream/InputStream 抽象父类、FileOutputStream 字节输出流三步走(创建流对象 → write() 写入 → close() 关闭)、四种构造方法、write(int/byte[]/byte[]+off+len) 三种写法、append 追加模式、getBytes() 字符串转字节、换行符 | course/Demo02、course/FileOutputStreamTest |
+| **字节输入流(IO)** | FileInputStream 读取三步走(创建 → read() 读 → close())、read()/read(byte[])/read(byte[]+off+len) 三种读法、read 返回 -1 表示 EOF、read(byte[]) 返回有效字节数、new String(bytes,0,length,UTF-8) 转字符串、try-catch-finally 关闭资源;文件复制 FileCopy(输入流读 + 输出流写 + byte[] 缓冲区循环 + 耗时统计) | course/Demo02、course/FileInputStreamTest、course/FileCopy |
+| **文件操作 File** | File 三种构造、`createNewFile()`/`mkdir()`/`mkdirs()`/`delete()`、`isFile()`/`isDirectory()`/`exists()`、`getAbsolutePath()`/`getName()`/`listFiles()`、绝对/相对路径与分隔符、IOException | course/Demo01 |
+
+---
+
+## 5. 随堂练习要点
+
+- 完成 `StudentSystemApp` 中**尚未实现**的菜单分支:课程管理、成绩管理、成绩排名、高级查询
+- 思考并改进 `CourseManager.addCourse` —— 用「try-catch 控制流程」的写法有什么问题?如何用 `contains` / 布尔标志改写?
+- 尝试在 `ScoreManager.countPass` 中把及格率输出为百分数(`* 100` 并保留两位小数)
+- 挑战 · 选做:用 **Stream 流**实现「高级查询」,如筛选出平均分 >= 85 的学生姓名列表(`filter + map + collect(Collectors.toList())`)
+- 比较 `RankUtil` 四种排序写法(TreeSet 自然排序 / 匿名内部类 / Lambda)各自的使用场景与优缺点
+- IO 流练习:用 `FileOutputStream` 往文件中写入一行字符串(先 `getBytes()` 转字节数组),再用 `append=true` 追加第二行,验证追加 vs 覆盖的区别;注意写完后 `close()` 关闭流
+- IO 流练习:用 `FileInputStream` 读取刚写入的文件并输出到控制台(`while ((len = fis.read(bytes)) != -1)` 循环读 + UTF-8 转字符串);再封装一个文件复制方法(输入流读 + 输出流写 + `byte[4096]` 缓冲区),复制一个视频文件并对比缓冲区 1024 / 4096 大小下的复制耗时
+- File 练习:分别用 `new File("a.txt")`、`new File("a", "b.txt")`、`new File(new File("a"), "b.txt")` 三种构造创建对象,并输出 `getAbsolutePath()` 对比路径差异;用 `mkdirs()` 创建多级目录后用 `listFiles()` 遍历查看;尝试 `createNewFile()` 并捕获 `IOException`
+- File 练习:思考 `mkdir()` 与 `mkdirs()` 在"父目录不存在"时的行为差异;`listFiles()` 返回的 `File[]` 数组如何用 foreach 取出每个子项的名字
+
+---
+
+## 6. 拓展阅读
+
+- **JDK 文档**:[`java.util.Comparator`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Comparator.html)、[`java.lang.Comparable`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Comparable.html)、[`java.io.File`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/File.html)
+- **接口默认方法**:若在 `Studyable` 中新增 `default` 方法,对已有实现类是否有影响?(接口演进问题)
+- **异常链**:`throw new RuntimeException(e)` 与 `catch (DuplicateStudentException e) { throw new RuntimeException(e); }` 的区别
+- **HashMap 分组**:`computeIfAbsent(key, k -> new ArrayList<>()).add(score)` 可以一行替代本作业中 `containsKey + put + get` 三步写法
+- **equals 契约**:为什么重写 equals 必须重写 hashCode?(HashMap/TreeSet 去重与查找的正确性)
+- **分层设计**:本作业的 entity/manager/main 分层是后续**三层架构**(Controller/Service/DAO)与 **MVC** 的雏形
+- **File 与 IO 流的关系**:File 只管理路径,真正的文件读写要用 **IO 流**(`FileInputStream` / `FileOutputStream` / `FileReader` / `FileWriter`),File 常作为流构造方法的参数传入,下一课将进入 IO 流学习
+- **路径分隔符的跨平台问题**:硬编码 `\` 在 Linux 上会失效,推荐用 `File.separator` 或 `File.pathSeparator` 获取系统对应的分隔符
+- **IO 流进阶方向**:本日学了字节流的读写(FileOutputStream 写 / FileInputStream 读 / FileCopy 复制),后续会学字符流(Reader/Writer,适合纯文本避免乱码)、缓冲流(BufferedInputStream/BufferedOutputStream 效率更高)、转换流,以及 JDK7+ 的 **try-with-resources**(`try (FileInputStream fis = new FileInputStream(...)) { }` 可自动 close 释放资源,替代手写 finally close)
+

+ 96 - 0
课堂练习题/20260806-File文件操作练习.md

@@ -0,0 +1,96 @@
+# 2026-08-06 File 文件操作类练习(课堂练习用)
+
+> **说明**:练习今日新学知识点——**文件操作类 `java.io.File`** 的常用操作:创建(`mkdir` / `mkdirs` / `createNewFile`)、删除(`delete`)、判断(`isFile` / `isDirectory` / `exists`)、获取(`getName` / `getPath` / `getAbsolutePath` / `listFiles`)与 `IOException` 处理。
+
+---
+
+## 练习:文件管理器(File 常用操作)
+
+**难度**:⭐⭐⭐
+**知识点**:`new File(String)` 构造方法、`mkdir` / `mkdirs`、`createNewFile`、`delete`、`isFile` / `isDirectory` / `exists`、`getName` / `getPath` / `getAbsolutePath` / `listFiles`、`IOException`
+
+**场景描述**:用 File 类封装一个路径名,再通过方法把它变成真实的文件/文件夹。写一个简单的「文件管理器」,用**最常用的构造方法 `new File("路径字符串")`** 创建对象,完成下面的常用操作。
+
+### 代码框架
+
+```java
+import java.io.File;
+import java.io.IOException;
+
+public class FileManager {
+    public static void main(String[] args) throws IOException {
+        // 最常用的构造方法:直接把路径字符串封装成 File 对象
+        File dir = new File("./");              // 当前项目目录
+        File folder = new File("myDir");        // 要创建的文件夹
+        File file = new File("hello.txt");      // 要创建的文件
+
+        // TODO 1: 创建文件夹——folder.mkdir() 单级创建;
+        //          多级用 new File("a/b/c").mkdirs()(父目录不存在也会创建)
+
+        // TODO 2: 创建文件——file.createNewFile(),文件已存在返回 false
+        //   提示:createNewFile() 抛 IOException,main 已声明 throws,也可用 try-catch
+
+        // TODO 3: 判断——输出 file.isFile() / file.isDirectory() / file.exists()
+
+        // TODO 4: 获取——输出 file.getName() / file.getPath() / file.getAbsolutePath()
+
+        // TODO 5: 删除——file.delete(),注意只能删空目录
+
+        // TODO 6: 遍历目录——dir.listFiles() 返回 File[],用 foreach 输出每个子项的名字
+    }
+}
+```
+
+**思考题**:
+1. `mkdir()` 和 `mkdirs()` 有什么区别?为什么创建多级目录更推荐 `mkdirs()`?(提示:父目录不存在时 mkdir 返回 false)
+2. `createNewFile()` 为什么需要处理 `IOException`?(提示:编译期异常,创建文件可能失败)
+3. `listFiles()` 返回什么类型?目录不存在时返回什么?(提示:`File[]`;返回 null)
+4. `delete()` 删除非空目录会成功吗?(提示:不会,返回 false)
+5. 绝对路径与相对路径有什么区别?`./` 表示什么?(提示:绝对路径从盘符开始,相对路径相对于当前项目)
+
+---
+
+# 参考答案要点
+
+```java
+// TODO 1: 创建文件夹
+System.out.println("mkdir 结果:" + folder.mkdir());           // true
+System.out.println("mkdirs 结果:" + new File("a/b/c").mkdirs()); // true(父目录一并创建)
+
+// TODO 2: 创建文件
+System.out.println("createNewFile 结果:" + file.createNewFile()); // 首次 true
+
+// TODO 3: 判断
+System.out.println("isFile:" + file.isFile());          // true
+System.out.println("isDirectory:" + file.isDirectory()); // false
+System.out.println("exists:" + file.exists());           // true
+
+// TODO 4: 获取
+System.out.println("名称:" + file.getName());            // hello.txt
+System.out.println("相对路径:" + file.getPath());        // hello.txt
+System.out.println("绝对路径:" + file.getAbsolutePath()); // 完整盘符路径
+
+// TODO 5: 删除
+System.out.println("删除结果:" + file.delete());         // true
+
+// TODO 6: 遍历目录
+File[] files = dir.listFiles();
+for (File f : files) {
+    System.out.println(f.getName() + (f.isDirectory() ? " <文件夹>" : " <文件>"));
+}
+```
+
+---
+
+# 涵盖知识点总览
+
+| 知识点 | 说明 |
+|--------|------|
+| `new File(String)` 构造方法 | 最常用,路径字符串 → File 对象 |
+| 绝对路径 vs 相对路径 | 绝对路径从盘符开始;相对路径相对于当前项目(`./`) |
+| mkdir / mkdirs | 单级 / 多级创建,父目录不存在时 mkdir 失败 |
+| createNewFile + IOException | 创建文件,编译期异常需处理 |
+| delete | 删除文件/空目录,非空目录返回 false |
+| isFile / isDirectory / exists | 判断文件/文件夹/是否存在 |
+| getName / getPath / getAbsolutePath | 获取名称、相对路径、绝对路径 |
+| listFiles 返回 File[] | 遍历目录,null 表示路径不存在 |