Explorar el Código

feat: 新增20260804课程内容——函数式编程与Stream流(Demo01~Demo02 map映射/Student实体/MethodDemo方法引用/ToMapDemo收集Map,练习三PageTest分页截取limit),0803课后作业参考解答(p4_lambda Lambda表达式接口、p5_employee员工管理排序),含课后作业/课堂笔记/课堂练习

WanJL hace 1 semana
padre
commit
adb196fbf3

+ 204 - 0
授课代码/c260804/src/course/Demo01.java

@@ -0,0 +1,204 @@
+package course;
+
+import java.util.*;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title Demo01
+ * @description 函数式编程和Stream流
+ * @create 2026/8/4
+ */
+public class Demo01 {
+    /*
+        函数式编程是一种编程范式,核心思想就是把计算看作是数学中的函数计算,
+        在函数式编程的过程中,程序由一些数学函数的组合构成,通过组合这些函数来完成计算任务。
+        而不是像命令式的语句序列一样来达到目的。
+        特点:
+            不可变性:变量只能被赋值一次,在他的生命周期内保持不变。这样可以减少程序中出现的错误,更好维护。
+            高阶函数:函数式编程支持传递函数作为参数,以及返回函数的方式。通过这种高阶函数,可以创建更加通用的函数。
+            函数组合:可以通过函数组合来创建新的函数,这也是函数式编程的核心思想。函数组合允许通过组合多个小的、独立的函数构建更大的、更复杂的函数。
+            无副作用:函数的执行结果只是取决于输入的参数,不会对函数外部的环境产生影响。
+            延迟求值:函数式编程一般经常采用延迟求职的策略,也就是说最终值只有在必要的时候才会被计算,提供程序的执行效率。
+        函数式编程一般都是使用函数作为程序的基本构建模块,重视的时候代码的抽象和模块化,以及对于函数的复合、抽象、变化。
+        一般用在并发编程、异步编程、数据处理以及各种机器学习和数据科学等等方向中。
+        Java中使用的就是lambda表达式,就是一种函数式编程的体现。lambda属于JDK8的一种语法糖
+        (参数)->{代码块} 这就是Java语法糖
+
+
+        Stream流
+            案例:
+                按照下面的要求完成集合的创建和遍历
+                    - 创建一个集合,存储多个字符串元素
+                    - 把集合中所有以"万"开头的元素存储到一个新的集合
+                    - 把"万"开头的集合中的长度为3的元素存储到一个新的集合
+                    - 遍历上一步得到的集合
+        使用我们以前学过的写法:
+                ArrayList<String> list = new ArrayList<>();
+                list.add("万震山");
+                list.add("万梓良");
+                list.add("万事如意");
+                list.add("王老五");
+                list.add("万巨龙");
+                list.add("张三");
+                //- 把集合中所有以"万"开头的元素存储到一个新的集合
+                ArrayList<String> list02 = new ArrayList<>();
+                for (String s: list){
+                    if (s.startsWith("万")){
+                        list02.add(s);
+                    }
+                }
+                //- 把"万"开头的集合中的长度为3的元素存储到一个新的集合
+                ArrayList<String> list03 = new ArrayList<>();
+                for (String s: list){
+                    if (s.startsWith("万")&&s.length()==3){
+                        list03.add(s);
+                    }
+                }
+                //- 遍历上一步得到的集合
+                System.out.println(list03);
+
+        如果采用Stream流的方式:
+            ArrayList<String> list = new ArrayList<>(List.of("万震山","万梓良", "万事如意","王老五","万巨龙","张三"));
+
+            //Stream流-把集合中所有以"万"开头的元素存储到一个新的集合
+            list.stream().filter(s->s.startsWith("万")).forEach(System.out::println);
+            //- 把"万"开头的集合中的长度为3的元素存储到一个新的集合
+            list.stream()
+                    .filter(s->s.startsWith("万"))   //过滤万 开头
+                    .filter(s->s.length()==3)   //过滤 长度为3的
+                    .forEach(System.out::println);  //遍历输出
+
+        Stream流的好处:
+            可以直接阅读代码的字面意思就可以展示跟逻辑无关的代码语义。比如获取流、过滤姓万的、过滤长度为3的,以及遍历输出。
+            list    --->集合对象
+            .stream()   --->    获取流
+            .filter(s->s.startsWith("万"))   ---> 过滤(过滤方式是找 万 开头)
+            .forEach(System.out::println);  ---> foreach遍历(输出)
+        Stream流才是真正的把函数式编程引入到Java中。
+
+
+        Stream流的思想:
+            Stream流总体来说,分为三类方法:
+                1、获取方法--获取Stream流
+                    |- 创建一条流水线,并且把数据放在流水线上准备进行操作。
+                2、中间方法--处理Stream流
+                    |- 流水线上的操作,一次操作完成后,还可以继续进行其他操作
+                3、终结方法--执行最后的操作
+                    |-一个Stream流水线只能有一个终结方法,是流水线上的最后一个操作。
+
+        (Stream)流:你可以理解为是一种流水线、水流。
+
+        生成Stream流的方式:
+            Collection体系集合
+                使用默认方法stream()生成流:  default Stream<E> stream()
+            Map体系集合:
+                把Map集合转换成Set,间接的生成流
+            数组也可以
+                通过Arrays中的静态方法tream生成流  public static IntStream stream(int[] array)
+            相同类型的多个数据
+                也可以通过Stream接口的静态方法of(T...value)生成流
+
+        Stream流中间操作方法:
+            中间操作的意思是执行完此方法后,Stream流依然可以继续执行其他操作。
+            常用的方法:
+                Stream<T> filter(Predicate<? super T> predicate)  用于对流的数据进行过滤
+                Stream<T> limit(long maxSize)   返回这个流中元素组成的流,截取前指定参数个数的数据。
+                Stream<T> skip(long n)  跳过指定参数个数的数据,返回由该流剩余元素组成的流
+                public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)    合并a和b两个流为一个流
+                Stream<T> distinct()    返回由该流的不同元素组成的流,根据Object.equals(obj)得到是否相同。
+
+
+        Stream流终结操作方法:
+            终结操作的意思就是执行完这个方法后,Stream流就不能再执行其他操作了。
+            void forEach(Consumer<? super T> action);   对此流的每个元素执行操作(不一定是输出)
+            long count();   返回此流中的元素数
+
+
+        Stream流收集操作方法:
+            当我们使用流对一些数据操作完毕后,可以把流中的数据收集到集合中。
+            <R, A> R collect(Collector<? super T, A, R> collector);     把结果收集到集合中
+            Object[] toArray(); 把结果收集到数组,生成一个新的结果集数组
+            default List<T> toList()    把结果收集到List集合,生成一个新的List集合,这里返回的List集合是不可变的集合。
+        工具类Collectors提供了具体的收集方式:
+
+            把元素收集到map集合
+           public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper)
+           把元素收集到List集合
+           public static <T> Collector<T, ?, List<T>> toList()
+           把元素收集到Set集合
+           public static <T> Collector<T, ?, Set<T>> toSet()
+
+
+
+
+
+
+
+
+
+
+
+     */
+    public static void main(String[] args) {
+        //Collection体系集合:使用默认方法stream()生成流:  default Stream<E> stream()
+        List<String> list=new ArrayList<>();
+        Stream<String> stream = list.stream();
+        //Map体系集合:把Map集合转换成Set,间接的生成流
+        Map<String,Object> map=new HashMap<>();
+        Stream<String> keyStream = map.keySet().stream();   //key就是Set集合
+        Stream<Object> valueStream = map.values().stream(); //value就是List集合
+        //数组也可以:通过Arrays中的静态方法tream生成流  public static IntStream stream(int[] array)
+        String[] array={"郑伊健","陈浩南", "齐夏","谢文东"};
+        Stream<String> arrayStream = Arrays.stream(array);
+        //相同类型的多个数据:也可以通过Stream接口的静态方法of(T...value)生成流
+        Stream<String> stringStream = Stream.of("长贵", "赵四", "刘能", "旺财");
+        Stream<Integer> integerStream = Stream.of(10, 15, 20, 35, 46);
+        /*
+        匿名内部类方式
+            stringStream.filter(
+                    new Predicate<String>() {
+                        @Override
+                        public boolean test(String s) {
+                            return s.startsWith("万");
+                        }
+                    }
+            );
+        */
+        //得到的新流,就是过滤后的
+        //Stream<String> newStream = stringStream.filter(s -> s.startsWith("万"));
+
+        //中间方法,往往会返回一个新的流对象,Stream对象,为了让我们可以继续进行下一步操作。
+        //返回这个流中元素组成的流,截取前指定参数个数的数据。
+        //Stream<String> newNewStream = newStream.limit(2);
+
+        //skip(long n)  跳过指定参数个数的数据,返回由该流剩余元素组成的流
+        //newNewStream.skip(1).forEach(System.out::println);
+        System.out.println("------------------------------------------");
+        //合并a和b两个流为一个流
+        Stream<String> concat = Stream.concat(arrayStream, stringStream);
+        //concat.forEach(System.out::println);
+        //Stream<T> distinct()    返回由该流的不同元素组成的流,根据Object.equals(obj)得到是否相同。
+        List<String> stringList = List.of("长贵", "赵四","郑伊健","陈浩南", "齐夏","刘能","郑伊健","陈浩南", "齐夏","郑伊健","陈浩南", "齐夏");
+        stringList.stream().distinct().forEach(System.out::println);
+
+
+        List<String> list1 = stringList  //集合对象
+                .stream()   //生成流
+                .distinct() //去重
+                .collect(Collectors.toList());//收集(收集成list集合)
+        System.out.println(list1);
+
+
+        Set<String> set = stringList  //集合对象
+                .stream()   //生成流
+                .filter(s -> s.contains("伊")) //去重
+                .collect(Collectors.toSet());//收集(收集成set集合)
+        System.out.println(set);
+    }
+
+}

+ 125 - 0
授课代码/c260804/src/course/Demo02.java

@@ -0,0 +1,125 @@
+package course;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title Demo02
+ * @description Stream流的选项-- map
+ * @create 2026/8/4
+ */
+public class Demo02 {
+    /*
+        如果有这样一个需求:
+            有一个学生集合,我们需要把学生集合里的每个学生的对象的name生成一个新的集合。
+            该怎么操作?
+            传统的操作集合的方式:
+                    Student s1 = new Student(1001, "项羽");
+                    Student s2 = new Student(1002, "张良");
+                    Student s3 = new Student(1003, "项庄");
+                    Student s4 = new Student(1004, "刘邦");
+                    Student s5 = new Student(1005, "萧何");
+                    Student s6 = new Student(1006, "韩信");
+                    Student s7 = new Student(1007, "樊哙");
+                    ArrayList<Student> list = new ArrayList<>(List.of(s1, s2, s3, s4, s5, s6, s7));
+                    ArrayList<String> nameList=new ArrayList<>();
+                    for (Student s:list){
+                        nameList.add(s.getName());
+                    }
+                    System.out.println(nameList);
+        lambda表达式的方式:
+
+        Student s1 = new Student(1001, "项羽");
+        Student s2 = new Student(1002, "张良");
+        Student s3 = new Student(1003, "项庄");
+        Student s4 = new Student(1004, "刘邦");
+        Student s5 = new Student(1005, "萧何");
+        Student s6 = new Student(1006, "韩信");
+        Student s7 = new Student(1007, "樊哙");
+        ArrayList<Student> list = new ArrayList<>(List.of(s1, s2, s3, s4, s5, s6, s7));
+
+        List<String> nameList = list    //集合对象
+                                .stream()   //获取流
+                                .map(student -> student.getName())  //获取每个学生对象,的 姓名
+                                .collect(Collectors.toList());//生成一个新的集合
+
+
+       方法引用:
+            引用类的静态方法:
+                类名::静态方法名     String::valueOf
+                使用前提:在重写方法的时候,方法体里面【只有一行代码。】并且这行代码是调用【某个类的静态方法】,
+                并且我们要把重写的【抽象方法中的所有参数都按照顺序传入到这个静态方法中】,这时候才可以使用 引用类的静态方法
+
+            引用对象的实例方法:(没有使用static修饰的方法)
+                对象名::方法名       sb::append
+                使用前提:在重写方法的时候,方法体里面【只有一行代码。】并且这行代码是调用【某个对象的成员方法】,
+                并且我们要把重写的【抽象方法中的所有参数都按照顺序传入到这个成员方法中】,这时候才可以使用 引用对象的实例方法
+
+            引用类的实例方法:
+                类名::方法名
+                使用前提:在重写方法的时候,方法体里面【只有一行代码。】并且这行代码是调用【某个类的成员方法】,
+                并且我们要把重写的【抽象方法中的所有参数都按照顺序传入到这个成员方法中】,这时候才可以使用 引用类的实例方法
+
+            引用类的构造方法:
+                类名::new
+                使用前提:如果方法体中的一行代码是构造器的话,就可以使用构造器引用,方法体里面【只有一行代码】。
+                并且这行代码是调用【某个类的构造方法】,并且我们要把重写的【抽象方法中的所有参数都按照顺序传入到这个构造方法中】,
+                这时候才可以使用 引用类的构造方法
+
+
+
+     */
+    public static void main(String[] args) {
+        Student s1 = new Student(1001, "项羽");
+        Student s2 = new Student(1002, "张良");
+        Student s3 = new Student(1003, "项庄");
+        Student s4 = new Student(1004, "刘邦");
+        Student s5 = new Student(1005, "萧何");
+        Student s6 = new Student(1006, "韩信");
+        Student s7 = new Student(1007, "樊哙");
+        ArrayList<Student> list = new ArrayList<>(List.of(s1, s2, s3, s4, s5, s6, s7));
+
+        List<String> nameList = list    //集合对象
+                                .stream()   //获取流
+                                .map(student -> student.getName())  //获取每个学生对象,的 姓名
+                                .collect(Collectors.toList());//生成一个新的集合
+        List<String> stringIdList = list.stream()
+                .map(s -> s.getId())
+                .map(String::valueOf)   // 相当于针对得到的Integer泛型的Stream流(integer集合) 里面的每一个 元素 创建String对象并且把元素转换成字符串valueOf
+                .collect(Collectors.toList());
+        System.out.println(stringIdList);
+
+
+        //把集合中每个学生的姓名追加到一个字符串中
+        StringBuilder sb=new StringBuilder();
+        list.stream()
+                .map(student -> student.getName())//这里获取的是学生的每个姓名组成的流(可以粗略的理解为是一个姓名的集合)
+                /*      简化之前---匿名内部类
+                .forEach(new Consumer<String>() {
+                    @Override
+                    public void accept(String s) {
+                        sb.append(s);
+                    }
+                })
+                */
+                /*  简化之后, lambda表达式+省略
+                .forEach(s->sb.append(s));
+                */
+                .forEach(sb::append);       //forEach--迭代出每一个元素,使用对象方法引用,针对每一个元素进行使用方法
+
+        System.out.println(sb);
+
+        //引用类的构造方法:
+        list.stream()
+                .map(name->name.getName())
+                .map(String::new)   //创建一个String对象  等价于 new String(name)
+                .forEach(System.out::println);
+    }
+}

+ 67 - 0
授课代码/c260804/src/course/MethodDemo.java

@@ -0,0 +1,67 @@
+package course;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title MethodDemo
+ * @description
+ * @create 2026/8/4
+ */
+public class MethodDemo {
+    //内部类--接口
+    public interface UseString{
+        /**
+         * 截取某个字符串的指定长度的子字符串的方法
+         * @param str
+         * @param start
+         * @param length
+         * @return
+         */
+        String use(String str,int start,int length);
+    }
+
+    /**
+     *
+     * @param name  姓名
+     * @param us    接口的实现类对象
+     * @return
+     */
+    public static String subName(String name,UseString us){
+        return us.use(name,0,1);
+    }
+
+    public static void main(String[] args) {
+        //传统的实现类实现接口的方式--1
+        UseStringImpl usi=new UseStringImpl();
+        String lastName = subName("张三", usi);
+        System.out.println(lastName);
+        //匿名内部类--2
+        String lastName02 = subName("李四", new UseString() {
+            @Override
+            public String use(String str, int start, int length) {
+                return str.substring(start, length);
+            }
+        });
+        System.out.println(lastName02);
+        //lambda表达式---3
+        String lastName03 = subName("李四", (str, start, length) -> str.substring(start, length));
+        System.out.println(lastName03);
+
+        //引用类的实例方法--4
+        String lastName04 = subName("王五", String::substring);
+        System.out.println(lastName04);
+
+    }
+
+}
+class UseStringImpl implements MethodDemo.UseString {
+
+    @Override
+    public String use(String str, int start, int length) {
+        //截取字符串,针对某个字符串对象,调用该方法,传入两个参数,第1个参数是从哪个字符串开始,第2个参数截取的字符串长度
+        return str.substring(start,length);
+    }
+}
+
+
+

+ 59 - 0
授课代码/c260804/src/course/Student.java

@@ -0,0 +1,59 @@
+package course;
+
+import java.util.Objects;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title Student
+ * @description
+ * @create 2026/8/4
+ */
+public class Student {
+    private Integer id;
+    private String name;
+
+    public Student() {
+    }
+
+    public Student(Integer id, String name) {
+        this.id = id;
+        this.name = name;
+    }
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == null || getClass() != o.getClass()) return false;
+        Student student = (Student) o;
+        return Objects.equals(id, student.id) && Objects.equals(name, student.name);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id, name);
+    }
+
+    @Override
+    public String toString() {
+        return "Student{" +
+                "id=" + id +
+                ", name='" + name + '\'' +
+                '}';
+    }
+}

+ 73 - 0
授课代码/c260804/src/course/ToMapDemo.java

@@ -0,0 +1,73 @@
+package course;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title ToMapDemo
+ * @description
+ * @create 2026/8/4
+ */
+public class ToMapDemo {
+    public static void main(String[] args) {
+        Student s1 = new Student(1001, "项羽");
+        Student s2 = new Student(1002, "张良");
+        Student s3 = new Student(1003, "项庄");
+        Student s4 = new Student(1004, "刘邦");
+        Student s5 = new Student(1005, "萧何");
+        Student s6 = new Student(1006, "韩信");
+        Student s7 = new Student(1007, "樊哙");
+        ArrayList<Student> list = new ArrayList<>(List.of(s1, s2, s3, s4, s5, s6, s7));
+
+        /*
+        public static <T, K, U> Collector<T, ?, Map<K,U>>
+        toMap
+        (
+        Function<? super T, ? extends K> keyMapper,
+        Function<? super T, ? extends U> valueMapper
+        )
+
+
+            贪心思想----局部最优解,不代表全局最优解
+
+         */
+
+
+
+        Map<Integer,Student>map=list    //集合对象
+            .stream()
+                .collect(Collectors.toMap(
+                        student->student.getId()//键
+                        ,
+                        student->student    //值
+                ));
+        System.out.println(map);
+
+        Map<Integer,Student>map1=list    //集合对象
+                .stream()
+                .collect(Collectors.toMap(
+                        new Function<Student, Integer>() {
+                            @Override
+                            public Integer apply(Student student) {
+                                return student.getId();
+                            }
+                        }
+                        ,
+                        new Function<Student, Student>() {
+                            @Override
+                            public Student apply(Student student) {
+                                return student;
+                            }
+                        }
+                ));
+
+
+
+
+    }
+}

+ 49 - 0
授课代码/c260804/src/exericse/exericse03/PageTest.java

@@ -0,0 +1,49 @@
+package exericse.exericse03;
+
+import java.util.List;
+import java.util.stream.Stream;
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title PageTest
+ * @description
+ * @create 2026/8/4
+ */
+
+public class PageTest {
+    public static void main(String[] args) {
+        // 1. 生成长度 0~9 的整数流,截取前 5 个(limit(5)),遍历输出 → 期望 0 1 2 3 4
+        //    TODO: Stream.of(0,1,2,3,4,5,6,7,8,9).limit(5).forEach(...)
+        Stream.of(0,1,2,3,4,5,6,7,8,9).limit(5).forEach(integer -> System.out.print(integer+" "));
+        System.out.println();
+
+
+        // 2. 生成长度 0~9 的整数流,跳过前 3 个(skip(3)),遍历输出 → 期望 3 4 5 6 7 8 9
+        Stream.of(0,1,2,3,4,5,6,7,8,9).skip(3).forEach(integer -> System.out.print(integer+" "));
+        System.out.println();
+        // 3. 分页:集合共 10 条数据,每页 3 条
+        //    第 1 页:skip(0).limit(3)  → 0 1 2
+        //    第 2 页:skip(3).limit(3)  → 3 4 5
+        //    第 3 页:skip(6).limit(3)  → 6 7 8
+        //    第 4 页:skip(9).limit(3)  → 9
+        //    TODO: 封装一个方法 showPage(List<Integer> list, int pageNo, int pageSize)
+        //          公式:skip((pageNo-1) * pageSize).limit(pageSize)
+        List<Integer> list = Stream.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9).toList();
+
+        showPage(list,1,3);
+        System.out.println();
+        showPage(list,2,3);
+        System.out.println();
+        showPage(list,3,3);
+        System.out.println();
+        showPage(list,4,3);
+
+    }
+    public static void showPage(List<Integer> list, int pageNo, int pageSize){
+        list.stream()
+                .skip((pageNo-1) * pageSize)
+                .limit(pageSize).forEach(integer -> {
+                    System.out.print(integer+" ");
+                });
+    }
+}

+ 12 - 0
授课代码/c260804/src/homework0803/p4_lambda/Addable.java

@@ -0,0 +1,12 @@
+package homework0803.p4_lambda;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title Addable
+ * @description
+ * @create 2026/8/4
+ */
+public interface Addable {
+    int add(int x, int y);
+}

+ 12 - 0
授课代码/c260804/src/homework0803/p4_lambda/Flyable.java

@@ -0,0 +1,12 @@
+package homework0803.p4_lambda;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title Flyable
+ * @description
+ * @create 2026/8/4
+ */
+public interface Flyable {
+    void fly(String s);
+}

+ 36 - 0
授课代码/c260804/src/homework0803/p4_lambda/LambdaDemo.java

@@ -0,0 +1,36 @@
+package homework0803.p4_lambda;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title LambdaDemo
+ * @description
+ * @create 2026/8/4
+ */
+public class LambdaDemo {
+
+    public static void useAdd(Addable a) {
+        System.out.println(a.add(10, 20));
+    }
+
+    public static void useFlyable(Flyable f) {
+        f.fly("今天天气不错...是风和日丽的...");
+    }
+
+    public static void main(String[] args) {
+        //当我们需要调用某个方法的时候,需要做哪些事情?
+        //1、先看所属的类
+        //2、再看修饰符(访问权限修饰符、静态修饰符)
+        //2.1 如果是private修饰符、那就不用想了, 肯定调用不了。
+        //2.2 如果是使用static修饰的,那就使用类名.方法名()调用,如果不是,就创建对象,用对象调用。
+        //3、看方法的参数,看参数是基本数据类型变量,还是引用数据类型变量。
+        //只要不是那8个基本数据类型,其他的都是引用数据类型变量。都需要在调用方法的时候传入 【对象】
+        // 传入的对象必须是该参数类型的,或者是该参数类型的子类、实现类的对象。 这就形成了多态
+
+        useAdd((x,y)->x+y);//我们调用这个方法,传入参数,有两种选择:
+            //1、自己重新建一个实现类,实现接口,重写方法。
+            //2、创建该接口的匿名内部类对象
+
+    }
+
+}

+ 83 - 0
授课代码/c260804/src/homework0803/p5_employee/Employee.java

@@ -0,0 +1,83 @@
+package homework0803.p5_employee;
+
+import java.util.Objects;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title Employee
+ * @description
+ * @create 2026/8/4
+ */
+public class Employee implements Comparable<Employee> {
+    private String id;      // 工号
+    private String name;    // 姓名
+    private double salary;  // 薪资
+    private String dept;    // 部门
+
+    public Employee(String id, String name, double salary, String dept) {
+        this.id = id;
+        this.name = name;
+        this.salary = salary;
+        this.dept = dept;
+    }
+
+    // TODO: 补全 getter / setter / toString 方法
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public double getSalary() {
+        return salary;
+    }
+
+    public void setSalary(double salary) {
+        this.salary = salary;
+    }
+
+    public String getDept() {
+        return dept;
+    }
+
+    public void setDept(String dept) {
+        this.dept = dept;
+    }
+
+    // TODO: 重写 compareTo —— 按薪资降序,薪资相同再按工号
+    @Override
+    public int compareTo(Employee o) {
+        // 提示:int result = Double.compare(o.getSalary(), this.salary);  // 降序
+        //       return result == 0 ? this.id.compareTo(o.getId()) : result;
+        int result = Double.compare(o.getSalary(), this.salary);
+        return result == 0 ? this.id.compareTo(o.getId()) : result;
+    }
+
+    // TODO: 重写 equals / hashCode —— 与 compareTo 判定口径一致(以工号判定)
+    //   提示:equals 用 Objects.equals(id, employee.id);hashCode 用 Objects.hash(id)
+
+    @Override
+    public boolean equals(Object obj) {
+        if ( obj instanceof Employee e){
+            return Objects.equals(id, e.id);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id);
+    }
+}

+ 54 - 0
授课代码/c260804/src/homework0803/p5_employee/EmployeeSystem.java

@@ -0,0 +1,54 @@
+package homework0803.p5_employee;
+
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.TreeSet;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title EmployeeSystem
+ * @description
+ * @create 2026/8/4
+ */
+public class EmployeeSystem {
+    public static void main(String[] args) {
+        // 1. 创建 TreeSet<Employee>(自然排序)
+        //    添加 5 名员工(其中两名工号相同,测试去重):
+        //    E001-张三-12000-技术部 / E002-李四-15000-市场部
+        //    E003-王五-15000-技术部 / E004-赵六-9000-财务部
+        //    E005-刘七-12000-市场部 / 再添加一个工号重复的 E002-李四-20000-市场部
+        TreeSet<Employee> treeSet=new TreeSet<>();
+        treeSet.add(new Employee("E001","张三",12000,"技术部"));
+        treeSet.add(new Employee("E002","李四",15000,"市场部"));
+        treeSet.add(new Employee("E003","王五",15000,"技术部"));
+        treeSet.add(new Employee("E004","赵六",9000,"财务部"));
+        treeSet.add(new Employee("E005","刘七",12000,"市场部"));
+        treeSet.add(new Employee("E002","李四",20000,"市场部"));
+        // 2. 迭代器遍历输出 → 观察:按薪资降序,薪资相同按工号;工号重复的自动去重
+        //    输出格式:"E001-张三-12000.0-技术部"
+        Iterator<Employee> iterator = treeSet.iterator();
+        while (iterator.hasNext()){
+            Employee e = iterator.next();
+            System.out.println(e.getId()+"-"+e.getName()+"-"+e.getSalary()+"-"+e.getDept());
+        }
+
+        // 3. 通讯录按姓名排序:重新创建 TreeSet,传入按姓名排序的 Comparator
+        //    (可用匿名内部类,也可尝试用 Lambda:Comparator.comparing(Employee::getName) 或
+        //    (o1, o2) -> o1.getName().compareTo(o2.getName()))
+        //    迭代器遍历输出
+        treeSet=new TreeSet<>(Comparator.comparing(Employee::getName));
+
+        treeSet.add(new Employee("E001","张三",12000,"技术部"));
+        treeSet.add(new Employee("E002","李四",15000,"市场部"));
+        treeSet.add(new Employee("E003","王五",15000,"技术部"));
+        treeSet.add(new Employee("E004","赵六",9000,"财务部"));
+        treeSet.add(new Employee("E005","刘七",12000,"市场部"));
+        treeSet.add(new Employee("E002","李四",20000,"市场部"));
+        iterator = treeSet.iterator();
+        while (iterator.hasNext()){
+            Employee e = iterator.next();
+            System.out.println(e.getId()+"-"+e.getName()+"-"+e.getSalary()+"-"+e.getDept());
+        }
+    }
+}

+ 830 - 0
课后作业/2026年8月4日-作业.md

@@ -0,0 +1,830 @@
+# 2026年8月4日 课后作业 — JavaSE 期末综合大作业:学生信息管理系统
+
+> **说明**:这是 **JavaSE 阶段的收尾综合大作业**,要求把整个阶段学过的知识点**串成一套完整的学生信息管理系统**,覆盖:**数据类型与运算符**、**流程控制(分支/循环)**、**类与对象(封装/构造方法)**、**继承与多态**、**抽象类与接口**、**自定义异常**、**集合框架(ArrayList/Map/Set/泛型/迭代器)**、**比较器排序(Comparable/Comparator)** 等核心内容;**Lambda 表达式 / 函数式编程 / Stream 流 / 方法引用** 作为"进阶挑战(选做)"给出**实现建议**,鼓励但不强制。
+>
+> 题目按 **模块** 划分:**模块一 ~ 模块二(⭐ 必做)** → **模块三 ~ 模块四(⭐⭐ 必做)** → **模块五(⭐⭐⭐⭐ 挑战·选做)**,最后附 **简答题(必做)**。请新建 Java 项目,按模块逐步编写并运行,将运行结果截图与代码一并提交。
+
+---
+
+## 📌 作业总体要求
+
+### 一、命名要求
+
+| 项目 | 要求 |
+|------|------|
+| **包名** | 按下列"推荐包结构"创建(`entity` / `exception` / `manager` / `util` / `main`) |
+| **类名** | 自由命名,但需**见名知意**(如 `Student`、`ScoreManager`) |
+| **测试入口** | 主程序统一命名为 `StudentSystemApp` |
+| **文件头注释** | 每个 Java 文件头部需包含 `@author`、`@title`、`@description`、`@create` 文档注释 |
+
+### 二、提交目录结构示例
+
+```
+提交\
+└─ 张三\
+    ├─ entity\                      ← 模块一:实体类 + 接口
+    │   ├─ Person.java              (抽象类)
+    │   ├─ Student.java
+    │   ├─ Teacher.java
+    │   ├─ Course.java
+    │   ├─ Score.java
+    │   ├─ Studyable.java           (接口)
+    │   └─ Teachable.java           (接口)
+    ├─ exception\                   ← 模块二:自定义异常
+    │   ├─ ScoreOutOfBoundsException.java
+    │   ├─ StudentNotFoundException.java
+    │   ├─ CourseNotFoundException.java
+    │   └─ DuplicateStudentException.java
+    ├─ manager\                     ← 模块三:核心功能(集合 + 流程控制)
+    │   ├─ StudentManager.java
+    │   ├─ CourseManager.java
+    │   └─ ScoreManager.java
+    ├─ util\                        ← 模块四:排序与排名
+    │   └─ RankUtil.java
+    ├─ main\                        ← 模块三:主菜单入口
+    │   └─ StudentSystemApp.java
+    └─ 简答题.txt                   ← 简答题答案
+```
+
+> ⚠ **注意**:包结构可微调(如合并包),但**逻辑分层必须清晰**:实体类 / 异常类 / 管理类 / 主程序要分开。
+
+---
+
+## 🏫 系统整体需求
+
+### 业务背景
+
+某学校需要一个控制台版**学生信息管理系统**,用于管理**学生、教师、课程、成绩**四类核心信息。要求通过 **Scanner 键盘输入 + 菜单** 交互,实现学生增删改查、课程维护、成绩录入与校验、成绩排名等功能。
+
+### 系统实体关系(一句话版)
+
+```
+Person(抽象类:name / age)
+  ├── Student(学号 id / 班级 clazz / 成绩列表 List<Score>)—— 学 → Studyable
+  └── Teacher(工号 id / 教授课程 Course)                  —— 教 → Teachable
+Course(课程编号 / 课程名 / 学分)
+Score (学号 / 课程编号 / 成绩 double,0~100)
+```
+
+### 功能模块总览
+
+| 模块 | 名称 | 必做程度 | 核心知识点 |
+|------|------|----------|-----------|
+| 模块一 | 实体类设计(Person/Student/Teacher/Course/Score + 接口) | ⭐ 必做 | 数据类型、类与对象、封装、构造方法、继承、抽象类、接口、多态 |
+| 模块二 | 自定义异常体系 | ⭐ 必做 | 自定义异常、throw/throws、try-catch、程序鲁棒性 |
+| 模块三 | 系统核心功能(学生/课程/成绩管理 + 主菜单) | ⭐⭐ 必做 | 集合框架、泛型、迭代器、Scanner、流程控制(switch/while/if/for) |
+| 模块四 | 排序与排名(Comparable / Comparator / TreeSet) | ⭐⭐ 必做 | 比较器排序、二次比较、Collections、compareTo 与 equals/hashCode 一致性 |
+| 模块五 | 函数式编程与 Stream 流高级查询 | ⭐⭐⭐⭐ 选做·挑战 | Lambda、Stream、方法引用(给出建议,不强制) |
+| 简答题 | 综合理论问答 | 必做 | 抽象类 vs 接口、equals/hashCode、异常机制等 |
+
+### 知识覆盖地图
+
+| 知识点 | 落地位置 | 说明 |
+|--------|---------|------|
+| 数据类型与运算符 | 模块一/三 | `String`(学号/姓名/课程名)、`int`(年龄/学分)、`double`(成绩/平均分/及格率)、算术/比较/逻辑运算符 |
+| 流程控制 | 模块三 | 主菜单 `switch`、外层 `while` 循环、输入校验 `if-else`、遍历 `for` / `foreach` |
+| 类与对象、封装 | 模块一 | `private` 成员 + getter/setter、全参构造方法 |
+| 继承 | 模块一 | `Student extends Person`、`Teacher extends Person`、`super(name, age)` |
+| 抽象类 | 模块一 | `Person` 抽象类 + 抽象方法 `getRole()` |
+| 接口 | 模块一 | `Studyable` / `Teachable` 接口,实现类重写抽象方法 |
+| 多态 | 模块三 | `Person[]` / `ArrayList<Person>` 装 Student+Teacher,`instanceof` 向下转型 |
+| 自定义异常 | 模块二/三 | 继承 `Exception`、`throw` 抛出、`throws` 声明、多 `catch` + `finally` |
+| 集合框架 | 模块三 | `ArrayList` 学生/课程/成绩、`HashMap<String, List<Score>>` 按学号分组 |
+| 泛型 | 模块三 | `ArrayList<Student>` 等泛型集合 |
+| 迭代器 | 模块三/四 | `Iterator` 遍历集合 |
+| 比较器排序 | 模块四 | `Comparable` 自然排序、`Comparator` 匿名内部类 / Lambda、`Collections.sort` |
+| equals/hashCode | 模块一 | 以"业务主键"(学号/工号/学号+课程)重写 |
+| String 类 | 模块一/三 | `equals`、`compareTo`、`format` 等 |
+| Lambda / Stream / 方法引用 | 模块五(选做) | `filter` / `map` / `count` / `distinct` / `collect(toMap)` / 方法引用 |
+
+---
+
+# 模块一:实体类设计(⭐ 必做)
+
+> **知识点**:数据类型、类与对象、封装、构造方法、继承、抽象类、接口、多态、equals/hashCode/toString
+>
+> **包名**:`entity`
+
+## 1.1 Person 抽象类
+
+要求定义一个抽象类 `Person`,作为"人"的公共抽象:
+
+```java
+// Person.java
+public abstract class Person {
+    private String name;   // 姓名
+    private int age;       // 年龄
+
+    public Person(String name, int age) {
+        this.name = name;
+        this.age = age;
+    }
+
+    // TODO: getter / setter
+
+    // TODO: 抽象方法 —— 返回角色描述("学生" / "教师")
+    public abstract String getRole();
+
+    @Override
+    public String toString() {
+        return "姓名:" + name + ",年龄:" + age;
+    }
+}
+```
+
+**要求**:
+1. `name` / `age` 用 `private` 封装,提供 getter/setter(封装知识点)。
+2. 抽象方法 `getRole()` 由子类实现。
+3. 重写 `toString()`(可结合 `getClass().getSimpleName()` 输出类名)。
+
+## 1.2 接口:Studyable / Teachable
+
+要求定义两个**接口**,分别规范"学生能学习、教师能授课":
+
+```java
+// Studyable.java —— 学习能力接口
+public interface Studyable {
+    void study();   // 学习
+}
+
+// Teachable.java —— 教学能力接口
+public interface Teachable {
+    void teach();   // 授课
+}
+```
+
+> 💡 **建议(不强制)**:可以给接口再补一个 **JDK8 默认方法**,例如 `default void prepare() { System.out.println("准备中..."); }`,体会接口默认方法的使用。
+
+## 1.3 Student 学生类
+
+`Student extends Person implements Comparable<Student>, Studyable`,核心要求:
+
+```java
+// Student.java
+import java.util.ArrayList;
+import java.util.List;
+
+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
+
+    // TODO: addScore(Score s) —— 向成绩列表添加一条成绩
+
+    // TODO: getTotalScore() —— 遍历 scores 累加总分(注意:scores 为空返回 0)
+    // TODO: getAvgScore()   —— 总分 / 成绩条数(保留两位小数;无成绩返回 0)
+    // TODO: getScoreCount() —— 返回成绩条数
+
+    // TODO: 实现抽象方法 getRole() —— 返回 "学生"
+
+    // TODO: 实现接口方法 study() —— 输出 "xxx 同学正在努力学习..."
+
+    // TODO: 重写 compareTo —— 按平均分【降序】,平均分相同再按学号
+    //   提示: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)
+
+    // TODO: 重写 toString —— 输出 学号/姓名/年龄/班级/平均分
+}
+```
+
+**要求**:
+1. 必须调用 `super(name, age)`(继承中构造方法的调用)。
+2. `compareTo` 用 `Double.compare` 实现降序 + 二次比较(学号)。
+3. `equals`/`hashCode` 以学号为判定依据,与 `compareTo` 判定口径一致。
+
+## 1.4 Teacher 教师类
+
+`Teacher extends Person implements Teachable`:
+
+```java
+// 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;
+    }
+
+    // TODO: getter / setter
+
+    // TODO: 实现 getRole() —— 返回 "教师"
+
+    // TODO: 实现 teach() —— 输出 "xxx 老师正在讲授《课程名》..."
+
+    // TODO: 重写 equals / hashCode —— 以【工号】判定
+
+    // TODO: 重写 toString
+}
+```
+
+## 1.5 Course 课程类
+
+```java
+// 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;
+    }
+
+    // TODO: getter / setter
+    // TODO: toString —— 输出 "C001-JavaSE 基础-3学分"
+}
+```
+
+## 1.6 Score 成绩类
+
+```java
+// 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 中校验并抛出异常
+    }
+
+    // TODO: getter / setter —— setScore 中校验:score < 0 || score > 100 时
+    //    throw new ScoreOutOfBoundsException("成绩必须在 0~100 之间,当前值:" + score);
+
+    // TODO: equals / hashCode —— 以 studentId + courseId 判定(同一学生同一课程视为一条成绩)
+
+    // TODO: toString
+}
+```
+
+> 📌 **注意**:`Score` 的构造方法签名带 `throws ScoreOutOfBoundsException`——这里引出了**模块二**的异常类,请先完成异常类再回来编译。
+>
+> 同时请思考:`Score` 类用到了哪些**数据类型**和**运算符**?(`double` 成绩、`||` 逻辑或、比较运算符等)
+
+## 1.7 模块一自测(多态 + 抽象类 + 接口综合验证)
+
+在 `main` 包下写一个临时测试类 `EntityTest`(只用于验证,不属于最终系统),完成以下验证:
+
+```java
+// EntityTest.java(自测用)
+public class EntityTest {
+    public static void main(String[] args) throws Exception {
+        // 1. 多态:Person[] 数组同时装 Student 和 Teacher
+        Person[] people = new Person[2];
+        people[0] = new Student("张三", 18, "S001", "Java2301");
+        people[1] = new Teacher("王老师", 35, "T001", new Course("C001", "JavaSE 基础", 3));
+
+        // 2. 遍历数组,调用 getRole()(动态绑定:编译看左,运行看右)
+        //    预期输出:张三 是 学生 / 王老师 是 教师
+
+        // 3. instanceof 向下转型
+        //    如果是 Student → 调用 study();如果是 Teacher → 调用 teach()
+    }
+}
+```
+
+**预期输出**:
+```
+张三(18 岁)的角色:学生
+王老师(35 岁)的角色:教师
+张三 同学正在努力学习...
+王老师 老师正在讲授《JavaSE 基础》...
+```
+
+**模块一思考题**(选做,不要求提交):
+1. `Person` 为什么设计成抽象类?如果不抽象,`new Person("李四", 20)` 合不合理?(提示:世界上没有"人"这个具体对象)
+2. `Student` 同时继承 `Person` 和实现 `Studyable`,这体现了"单继承多实现"的什么特点?
+3. `equals`/`hashCode` 为什么要以**学号**为判定依据?
+
+---
+
+# 模块二:自定义异常体系(⭐ 必做)
+
+> **知识点**:自定义异常、继承 `Exception`、`throw` 抛出、`throws` 声明、try-catch、程序鲁棒性
+>
+> **包名**:`exception`
+
+## 2.1 定义四个自定义异常
+
+均继承 `Exception`(编译期异常),提供无参构造 + 带消息构造:
+
+```java
+// ScoreOutOfBoundsException.java —— 成绩越界(0~100 之外)
+public class ScoreOutOfBoundsException extends Exception {
+    public ScoreOutOfBoundsException() { }
+    public ScoreOutOfBoundsException(String message) { super(message); }
+}
+
+// StudentNotFoundException.java —— 按学号查找学生时不存在
+public class StudentNotFoundException extends Exception {
+    public StudentNotFoundException() { }
+    public StudentNotFoundException(String message) { super(message); }
+}
+
+// CourseNotFoundException.java —— 按课程编号查找课程时不存在
+public class CourseNotFoundException extends Exception {
+    public CourseNotFoundException() { }
+    public CourseNotFoundException(String message) { super(message); }
+}
+
+// DuplicateStudentException.java —— 添加学生时学号已存在(重复)
+public class DuplicateStudentException extends Exception {
+    public DuplicateStudentException() { }
+    public DuplicateStudentException(String message) { super(message); }
+}
+```
+
+## 2.2 模块二自测:异常处理综合演练
+
+在 `main` 包下写临时测试类 `ExceptionTest`,验证异常机制:
+
+```java
+// ExceptionTest.java(自测用)
+public class ExceptionTest {
+    public static void main(String[] args) {
+        // 1. 正常成绩:new Score("S001", "C001", 88.5) → 输出成绩信息
+        // 2. 非法成绩:new Score("S001", "C001", 150) → 抛出 ScoreOutOfBoundsException
+        //    用 try-catch 捕获并输出错误消息:"成绩必须在 0~100 之间,当前值:150.0"
+        // 3. 同时抛多个异常(如 -5 和 120),验证多 catch 块各自独立捕获
+        // 4. 在 catch 后加 finally 块,输出"—— 成绩录入完成"
+    }
+}
+```
+
+**预期输出**:
+```
+========== 测试 1:正常成绩 ==========
+S001 同学 C001 课程成绩:88.5 分
+========== 测试 2:成绩越界 ==========
+成绩必须在 0~100 之间,当前值:150.0
+—— 成绩录入完成
+========== 测试 3:多异常捕获 ==========
+成绩必须在 0~100 之间,当前值:-5.0
+成绩必须在 0~100 之间,当前值:120.0
+```
+
+**模块二思考题**(选做):
+1. 自定义异常为什么要继承 `Exception` 而不是直接继承 `Object`?
+2. `throw` 和 `throws` 的区别是什么?
+3. 多 `catch` 块的顺序有什么要求?(提示:子类异常在前,父类异常在后)
+
+---
+
+# 模块三:系统核心功能(⭐⭐ 必做)
+
+> **知识点**:集合框架(`ArrayList` / `HashMap`)、泛型、迭代器、Scanner 键盘输入、流程控制(`switch` / `while` / `if-else` / `for`)、多态、自定义异常综合应用
+>
+> **包名**:`manager`(管理类)、`main`(主菜单)
+
+## 3.1 StudentManager 学生管理类
+
+内部用 `ArrayList<Student>` 保存所有学生,封装如下功能(方法签名仅供参考,可自行设计):
+
+```java
+// StudentManager.java
+import java.util.ArrayList;
+import java.util.Iterator;
+
+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),输出"添加成功"
+
+    // TODO: removeById(String id) —— 按学号删除学生
+    //   要求:找到并删除;找不到 → 抛 StudentNotFoundException("学生不存在:S001")
+
+    // TODO: findById(String id) —— 按学号查找,返回 Student;找不到返回 null
+    //   要求:必须使用迭代器 Iterator 遍历(复习迭代器知识点)
+
+    // TODO: printAll() —— 遍历输出所有学生(foreach 或迭代器)
+
+    // TODO: getStudents() —— 返回学生集合(供排序模块使用)
+}
+```
+
+> 💡 **建议(不强制)**:可以加一个 `updateStudent(String id, String newName, int newAge)` 修改方法(先 `findById`,再 `setName` / `setAge`),进一步练习"查改"流程。
+
+## 3.2 CourseManager 课程管理类
+
+内部用 `ArrayList<Course>` 保存所有课程,封装类似功能:
+
+```java
+// CourseManager.java
+public class CourseManager {
+    private ArrayList<Course> courses;
+
+    public CourseManager() {
+        this.courses = new ArrayList<>();
+    }
+
+    // TODO: addCourse(Course c) —— 添加课程(课程编号重复则不允许)
+    
+    // TODO: findById(String courseId) —— 按课程编号查找,返回 Course;找不到抛 CourseNotFoundException
+    
+    // TODO: removeById(String courseId) —— 按课程编号删除(找不到抛 CourseNotFoundException)
+    
+    // TODO: printAll() —— 遍历输出所有课程
+    
+    // TODO: getCourses()
+    
+}
+```
+
+## 3.3 ScoreManager 成绩管理类
+
+内部用 `ArrayList<Score>` 保存"成绩台账"(所有成绩),同时提供按学号分组统计功能:
+
+```java
+// ScoreManager.java
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+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"
+
+    // TODO: printAll() —— 遍历输出成绩台账(迭代器或 foreach)
+
+    // TODO: getStudentTotal(String studentId) —— 从 scoreMap 取该学生成绩列表求总分
+    // TODO: getStudentAvg(String studentId)   —— 求平均分
+    // TODO: countPass() —— 统计及格(>= 60)人数 / 及格率(用运算符计算百分比,保留两位小数)
+}
+```
+
+> 📌 **必做点**:`HashMap<String, List<Score>>` 的"按学号分组"必须实现——这是对 **Map 集合** 和 **ArrayList 嵌套** 的综合训练。
+
+## 3.4 StudentSystemApp 主菜单(流程控制)
+
+在 `main` 包编写程序入口,用 **Scanner + switch + while** 实现控制台菜单:
+
+```java
+// StudentSystemApp.java
+import java.util.Scanner;
+
+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
+        //    课程:C001-JavaSE 基础-3、C002-MySQL 数据库-2、C003-数据结构-3
+        //    教师:王老师/35/T001/C001
+
+        // 3. 主循环 + 主菜单(while + switch)
+        //    主菜单:
+        //    ===== 学生信息管理系统 =====
+        //    1. 学生管理
+        //    2. 课程管理
+        //    3. 成绩管理
+        //    4. 成绩排名
+        //    5. 高级查询(挑战·选做)
+        //    0. 退出
+        //    请输入你的选择:
+
+        // 4. 二级菜单(以学生管理为例):
+        //    ===== 学生管理 =====
+        //    1. 添加学生
+        //    2. 删除学生
+        //    3. 查找学生
+        //    4. 查看所有学生
+        //    0. 返回上级
+
+        // 5. 每个功能都用 Scanner 接收键盘输入,并 try-catch 处理自定义异常
+        //    (例如:添加重复学号 → 捕获 DuplicateStudentException 并提示)
+    }
+}
+```
+
+### 菜单交互示例(节选)
+
+```
+===== 学生信息管理系统 =====
+1. 学生管理
+2. 课程管理
+3. 成绩管理
+4. 成绩排名
+5. 高级查询(挑战·选做)
+0. 退出
+请输入你的选择:1
+
+===== 学生管理 =====
+1. 添加学生
+2. 删除学生
+3. 查找学生
+4. 查看所有学生
+0. 返回上级
+请输入你的选择:1
+请输入姓名:赵六
+请输入年龄:21
+请输入学号:S004
+请输入班级:Java2302
+添加成功:赵六
+```
+
+### 异常处理要求(必做)
+
+在"添加学生""删除学生""录入成绩"三个功能中,必须用 `try-catch` 处理自定义异常:
+
+| 操作 | 可能抛出的异常 | 提示 |
+|------|--------------|------|
+| 添加学生(学号重复) | `DuplicateStudentException` | 输出"学号已存在:S004" |
+| 删除/查找学生(不存在) | `StudentNotFoundException` | 输出"学生不存在:S999" |
+| 查找课程(不存在) | `CourseNotFoundException` | 输出"课程不存在:C999" |
+| 录入成绩(0~100 之外) | `ScoreOutOfBoundsException` | 输出"成绩必须在 0~100 之间,当前值:150.0" |
+
+**模块三思考题**(选做):
+1. 用 `ArrayList` 存储学生和用之前学的 `Student[]` 数组存储,最大的区别是什么?
+2. 为什么"查找学生"要推荐用**迭代器**?(提示:遍历时删除元素避免 `ConcurrentModificationException`)
+3. `HashMap<String, List<Score>>` 中,key 为什么用学号 String 而不是 Student 对象?(提示:简化 + 唯一)
+
+---
+
+# 模块四:排序与排名(⭐⭐ 必做)
+
+> **知识点**:`Comparable` 自然排序、`Comparator` 比较器(匿名内部类 / Lambda)、`Collections` 工具类、`TreeSet` 自动排序去重、迭代器遍历、compareTo 与 equals/hashCode 一致性
+>
+> **包名**:`util`
+
+## 4.1 需求说明
+
+在 `RankUtil` 中完成 4 个排序/排名功能,并在 `main` 包中写 `RankTest` 验证:
+
+```java
+// RankUtil.java
+import java.util.*;
+
+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 遍历输出,格式:排名-学号-姓名-平均分
+    }
+
+    /**
+     * 功能 2:按姓名排序 —— Comparator 匿名内部类
+     * 使用 Collections.sort(list, new Comparator<Student>() {...}),
+     * 匿名内部类中 o1.getName().compareTo(o2.getName())
+     */
+    public static void sortByName(StudentManager sm) {
+        // TODO: 拷贝一份学生列表,Collections.sort + 匿名内部类 Comparator 按姓名升序
+        //       遍历输出
+    }
+
+    /**
+     * 功能 3:按年龄排序 —— Comparator + Lambda
+     * 使用 Collections.sort(list, (s1, s2) -> Integer.compare(s1.getAge(), s2.getAge()));
+     */
+    public static void sortByAge(StudentManager sm) {
+        // TODO: 拷贝学生列表,用 Lambda 按年龄升序排序,遍历输出
+    }
+
+    /**
+     * 功能 4:按平均分降序(和自然排序对比)—— 用 Collections.sort + 匿名内部类
+     * 要求:不依赖 Student 的 compareTo,在 Comparator 中自己算平均分(Double.compare 降序)
+     */
+    public static void sortByAvgDesc(StudentManager sm) {
+        // TODO: Double.compare(o2.getAvgScore(), o1.getAvgScore()) 实现降序
+    }
+}
+```
+
+## 4.2 验证用例(RankTest)
+
+沿用模块三的示例数据,给部分学生录入成绩后,调用 `RankUtil` 的 4 个方法,预期输出如下:
+
+```
+========== 功能 1:成绩排行榜(TreeSet 自然排序) ==========
+第1名:S001 张三 平均分 90.0
+第2名:S002 李四 平均分 85.0
+第3名:S003 王五 平均分 78.0
+========== 功能 2:按姓名排序(Comparator 匿名内部类) ==========
+S001 张三
+S002 李四
+S003 王五
+========== 功能 3:按年龄排序(Comparator + Lambda) ==========
+S001 张三 18 岁
+S002 李四 19 岁
+S003 王五 20 岁
+========== 功能 4:按平均分降序(Collections.sort) ==========
+S001 张三 90.0
+S002 李四 85.0
+S003 王五 78.0
+```
+
+> 📌 以上输出仅为示意,实际内容取决于你录入的数据,但**排序规则必须正确**。
+
+## 4.3 必答原理分析(写在代码注释或提交说明中)
+
+1. `Student` 重写 `compareTo` 后,放入 `TreeSet` 会发生什么?(提示:自动排序 + 按 compareTo 返回值去重)
+2. 为什么 `equals`/`hashCode` 以学号判定,`compareTo` 也要保证学号相同返回 0?(提示:compareTo 与 equals/hashCode 判定口径一致性,官方推荐 `compareTo == 0 → equals 返回 true`)
+3. 临时按姓名排序时,为什么不修改 `Student` 类?(提示:用 `Comparator` 在集合这边定义排序规则——`Collections.sort(list, comparator)` 或 `TreeSet(comparator)`)
+
+**模块四思考题**(选做):
+1. `Collections.sort(list)` 和 `TreeSet` 自动排序有什么区别?(提示:前者改已有 List,后者存入时排序+去重)
+2. 功能 3 的 Lambda `(s1, s2) -> Integer.compare(...)` 等价于哪个方法引用写法?(提示:`Comparator.comparingInt(Student::getAge)`)
+3. 如果只按平均分排序(不二次比较学号),两个平均分相同的学生能同时存入 `TreeSet` 吗?
+
+---
+
+# 模块五:函数式编程与 Stream 流高级查询(⭐⭐⭐⭐ 挑战·选做)
+
+> **知识点**:Lambda 表达式、函数式接口(Predicate / Consumer / Function)、Stream 流(filter / map / count / distinct / sorted / collect)、方法引用
+>
+> **说明**:本模块为**选做挑战**,给出**实现建议**,不强制要求。学有余力的同学建议完成,它是对今日(0804)所学的 **Stream 流与函数式编程** 的实战检验。
+
+## 5.1 需求描述
+
+在 `util` 包新增 `StreamQuery` 类,用 Stream 流完成 6 个高级查询(每个查询都是**一条 Stream 链**):
+
+```java
+// StreamQuery.java
+import java.util.*;
+import java.util.stream.Collectors;
+
+public class StreamQuery {
+
+    /**
+     * 查询 1:及格学生名单 —— filter + forEach
+     * 过滤出平均分 >= 60 的学生,输出"学号-姓名-平均分"
+     * 建议:list.stream().filter(s -> s.getAvgScore() >= 60).forEach(s -> System.out.println(...));
+     */
+    public static void queryPass(List<Student> list) { }
+
+    /**
+     * 查询 2:所有学生姓名 —— map + collect(toList)
+     * 把学生流映射成姓名流,收集成 List<String> 输出
+     * 建议:list.stream().map(Student::getName).collect(Collectors.toList())
+     */
+    public static void queryNames(List<Student> list) { }
+
+    /**
+     * 查询 3:及格人数统计 —— filter + count
+     * 建议:long cnt = list.stream().filter(s -> s.getAvgScore() >= 60).count();
+     */
+    public static void countPass(List<Student> list) { }
+
+    /**
+     * 查询 4:班级去重 —— map + distinct
+     * 取出所有班级名,去重后输出
+     * 建议:list.stream().map(Student::getClazz).distinct().forEach(System.out::println);
+     */
+    public static void distinctClazz(List<Student> list) { }
+
+    /**
+     * 查询 5:学号 → 学生 映射 —— collect(Collectors.toMap)
+     * 收集成 Map<String, Student>(键=学号,值=学生对象)
+     * 建议:Map<String, Student> map = list.stream()
+     *           .collect(Collectors.toMap(Student::getId, s -> s));
+     */
+    public static void toStudentMap(List<Student> list) { }
+
+    /**
+     * 查询 6:按平均分降序排名 —— sorted + Comparator.comparingDouble 方法引用
+     * 建议:list.stream()
+     *           .sorted(Comparator.comparingDouble(Student::getAvgScore).reversed())
+     *           .forEach(...);
+     */
+    public static void rankByStream(List<Student> list) { }
+}
+```
+
+## 5.2 预期效果(示意)
+
+```
+========== 查询 1:及格学生 ==========
+S001-张三-90.0
+S002-李四-85.0
+========== 查询 2:所有学生姓名 ==========
+[张三, 李四, 王五]
+========== 查询 3:及格人数 ==========
+及格人数:2
+========== 查询 4:班级去重 ==========
+Java2301
+Java2302
+========== 查询 5:学号→学生 Map ==========
+{S001=Student{...}, S002=Student{...}, S003=Student{...}}
+========== 查询 6:Stream 排名 ==========
+S001 张三 90.0
+S002 李四 85.0
+S003 王五 78.0
+```
+
+## 5.3 进阶挑战提示(建议)
+
+1. **方法引用**:`Student::getName` 等价于 `s -> s.getName()`;`System.out::println` 等价于 `s -> System.out.println(s)`。
+2. **分页查询**(可选):`skip((pageNo - 1) * pageSize).limit(pageSize)` 实现成绩排行榜分页。
+3. **Lambda 与匿名内部类对比**(可选):查询 5 可先写 `new Function<Student, String>() {...}` 匿名内部类版本,再改成 Lambda 版本,体会简化。
+4. **函数式接口**(可选):自定义一个函数式接口(如 `@FunctionalInterface interface ScoreFilter { boolean test(Student s); }`),把"过滤规则"作为参数传递——体会函数式编程"把函数作为参数"的思想。
+
+---
+
+# 简答题(必做)
+
+请用文字说明(可配合示例代码)回答以下问题:
+
+## 1. 抽象类与接口的区别
+
+从以下维度对比 `抽象类` 和 `接口`:
+- 关键字与定义方式
+- 能否有构造方法
+- 成员变量(常量 vs 变量)
+- 成员方法(抽象/普通/默认/静态/私有方法)
+- 继承/实现关系(单继承 vs 多实现)
+- 适用场景("is-a" 关系 vs "has-a 能力"关系)
+
+## 2. equals / hashCode / compareTo 三者的一致性
+
+- 重写 `equals` 为什么必须重写 `hashCode`?
+- `compareTo == 0` 与 `equals 返回 true` 有什么关系?本作业中学生类为什么统一以"学号"判定?
+
+## 3. Comparable 与 Comparator 的区别
+
+- 分别作用在哪个位置(类内部 / 类外部)?
+- 什么时候用 `Comparable`,什么时候用 `Comparator`?
+- 临时按不同字段排序时应该用哪个?(提示:不用改实体类)
+
+## 4. 自定义异常与异常处理流程
+
+- 自定义异常为什么要继承 `Exception` 或 `RuntimeException`?两者区别?
+- `throw` 和 `throws` 的区别?
+- 项目中"录入成绩越界、查找学生失败"等业务错误,为什么适合用自定义异常而不是返回 null / false?(提示:语义清晰 + 强制调用方处理)
+
+## 5.(选做加分)Lambda 表达式与匿名内部类的区别
+
+从三个维度回答:
+- 需要的类型(接口 / 抽象类 / 普通类)
+- 使用限制(抽象方法个数)
+- 编译原理(是否产生独立 .class 文件)
+
+---
+
+# 📊 完成标准与评分参考
+
+| 评分维度 | 达标标准 | 分值参考 |
+|---------|---------|---------|
+| 模块一 实体类 | 抽象类/接口/继承/多态/equals/hashCode 全部正确,能编译运行 | 25 分 |
+| 模块二 异常 | 4 个自定义异常 + try-catch 正确使用 | 15 分 |
+| 模块三 核心功能 | 菜单可运行,增删改查 + 成绩录入校验 + Map 分组全部实现 | 30 分 |
+| 模块四 排序排名 | TreeSet / Comparator / Collections 三种排序正确 | 20 分 |
+| 简答题 | 表达清楚、要点完整 | 10 分 |
+| 模块五 Stream(选做) | 6 个查询正确,可额外加分 | 加分 10 分 |
+
+> ✅ **提交清单**:全部 Java 源码 + 各模块运行结果截图 + 简答题文档 + 提交目录结构与命名合规。
+
+---
+
+## 🧭 做题顺序建议(供参考)
+
+1. **先写实体类**(模块一)→ 再写异常类(模块二)→ 编译通过。
+2. 然后写**管理类 + 主菜单**(模块三),先打通"添加/查看/删除/成绩录入"流程。
+3. 再补**排序排名**(模块四),用之前准备好的示例数据验证排序规则。
+4. 最后**学有余力再做** Stream 高级查询(模块五)。
+5. 边写边用 git 提交,保持每个模块一个 commit,方便回溯。

+ 1019 - 0
课堂笔记/01-JavaSE基础-笔记/20260804-笔记.md

@@ -0,0 +1,1019 @@
+# 20260804 课堂笔记 — 函数式编程(Functional Programming)+ Stream 流(中间操作 filter/limit/skip/map/concat/distinct + 终结操作 forEach/count + 收集操作 collect/Collectors:toList/toSet/toMap + 方法引用四种形式)+ 20260803 课后作业回顾(Lambda 应用 / Employee 员工排序)
+
+- **日期**:2026-08-04
+- **项目**:`c260804`
+- **包路径**:`course` / `homework0803`
+- **作者**:WanJL
+
+---
+
+## 目录
+
+1. [函数式编程思想:概念与特点](#1-函数式编程思想概念与特点)
+2. [Lambda 表达式回顾(JDK8 语法糖)](#2-lambda-表达式回顾jdk8-语法糖)
+3. [Stream 流入门案例:筛选"万"开头 + 长度为 3](#3-stream-流入门案例筛选万开头--长度为-3)
+4. [Stream 流的思想:三类方法(获取 / 中间 / 终结)](#4-stream-流的思想三类方法获取--中间--终结)
+5. [Stream 流中间操作方法:filter / limit / skip / map / concat / distinct](#5-stream-流中间操作方法filter--limit--skip--map--concat--distinct)
+6. [方法引用(Method Reference):四种形式](#6-方法引用method-reference四种形式)
+7. [Stream 流终结操作方法:forEach / count](#7-stream-流终结操作方法foreach--count)
+8. [Stream 流收集操作方法:collect + Collectors(toList / toSet / toMap)](#8-stream-流收集操作方法collect--collectorstolist--toset--tomap)
+9. [生成 Stream 流的四种方式](#9-生成-stream-流的四种方式)
+10. [20260803 课后作业回顾:Lambda 表达式应用(Addable / Flyable)](#10-20260803-课后作业回顾lambda-表达式应用addable--flyable)
+11. [20260803 课后作业回顾:Employee 员工管理(TreeSet 自然排序 + Comparator 按姓名 + 方法引用)](#11-20260803-课后作业回顾employee-员工管理treeset-自然排序--comparator-按姓名--方法引用)
+12. [随堂练习要点](#12-随堂练习要点)
+13. [拓展阅读](#13-拓展阅读)
+
+---
+
+## 1. 函数式编程思想:概念与特点
+
+### 概念
+
+**函数式编程**是一种**编程范式**,核心思想就是把计算看作是数学中的**函数计算**——程序由一些**数学函数的组合**构成,通过组合这些函数来完成计算任务;而不是像命令式那样写一条一条的**语句序列**来达到目的。
+
+```java
+// 来源:course/Demo01.java(注释部分)
+/*
+    函数式编程是一种编程范式,核心思想就是把计算看作是数学中的函数计算,
+    在函数式编程的过程中,程序由一些数学函数的组合构成,通过组合这些函数来完成计算任务。
+    而不是像命令式的语句序列一样来达到目的。
+*/
+```
+
+### 函数式编程的五大特点
+
+| 特点 | 说明 |
+|------|------|
+| **不可变性** | 变量只能被赋值一次,在其生命周期内保持不变,减少程序错误,更好维护 |
+| **高阶函数** | 支持把**函数作为参数传递**,以及**把函数作为返回值**,可创建更通用的函数 |
+| **函数组合** | 通过组合多个小的、独立的函数构建更大、更复杂的函数(核心思想) |
+| **无副作用** | 函数执行结果**只取决于输入的参数**,不会对函数外部环境产生影响 |
+| **延迟求值** | 最终值只在**必要的时候**才被计算,提高程序执行效率 |
+
+### 应用场景
+
+> 一般用在**并发编程、异步编程、数据处理**以及各种**机器学习和数据科学**等方向中。
+
+### Java 中的体现
+
+Java 中使用 **Lambda 表达式**就是一种函数式编程的体现,**Lambda 属于 JDK8 的一种语法糖**:
+
+```java
+// 来源:course/Demo01.java(注释部分)
+(参数) -> {代码块}    // 这就是 Java 语法糖
+```
+
+### 关键理解
+
+1. **函数式编程 ≠ 新语言**:Java 通过 Lambda 表达式把函数式编程"引进来",它是对**匿名内部类**的语法简化。
+2. **与面向对象的对比**:面向对象强调"通过对象的形式做事情",函数式编程强调"**做什么**",忽略繁琐的"以什么形式去做"。
+3. **`Stream 流`才是真正把函数式编程引入 Java**(详见第 3~9 节)。
+
+---
+
+## 2. Lambda 表达式回顾(JDK8 语法糖)
+
+昨天(0803)已经学习了 Lambda 的**标准格式、省略模式、使用前提**,今天是它的应用延续。
+
+### 标准格式回顾
+
+```java
+(形式参数) -> {代码块}
+```
+
+**三要素**:形式参数、箭头 `->`、代码块。
+
+### 使用前提(回顾)
+
+1. **必须要有接口**——Lambda 的本质是简化接口的匿名实现
+2. **接口中有且只有一个抽象方法**——即必须是**函数式接口**
+
+### 与匿名内部类的区别(回顾)
+
+| 对比维度 | 匿名内部类 | Lambda 表达式 |
+|----------|-----------|---------------|
+| 需要的类型 | 接口、抽象类、普通具体类均可 | **只能是接口** |
+| 使用限制 | 多抽象方法也能用 | **必须只有一个抽象方法** |
+| 实现原理 | 编译后产生独立 `.class` 文件 | 编译后**不产生单独 `.class` 文件**,运行期动态生成字节码 |
+
+---
+
+## 3. Stream 流入门案例:筛选"万"开头 + 长度为 3
+
+### 案例需求
+
+按照下面的要求完成集合的创建和遍历:
+
+1. 创建一个集合,存储多个字符串元素
+2. 把集合中所有以 **"万"开头** 的元素存储到一个新的集合
+3. 把"万"开头的集合中**长度为 3** 的元素存储到一个新的集合
+4. 遍历上一步得到的集合
+
+### 传统写法(以前学过的写法)
+
+```java
+// 来源:course/Demo01.java(注释部分:传统 for-each 写法)
+ArrayList<String> list = new ArrayList<>();
+list.add("万震山");
+list.add("万梓良");
+list.add("万事如意");
+list.add("王老五");
+list.add("万巨龙");
+list.add("张三");
+
+// - 把集合中所有以"万"开头的元素存储到一个新的集合
+ArrayList<String> list02 = new ArrayList<>();
+for (String s : list) {
+    if (s.startsWith("万")) {
+        list02.add(s);
+    }
+}
+
+// - 把"万"开头的集合中的长度为3的元素存储到一个新的集合
+ArrayList<String> list03 = new ArrayList<>();
+for (String s : list) {
+    if (s.startsWith("万") && s.length() == 3) {
+        list03.add(s);
+    }
+}
+
+// - 遍历上一步得到的集合
+System.out.println(list03);
+```
+
+**缺点**:要写**好几个循环** + **好几个中间集合**,代码繁琐、语义不直观。
+
+### Stream 流写法
+
+```java
+// 来源:course/Demo01.java(注释部分:Stream 写法)
+ArrayList<String> list = new ArrayList<>(List.of("万震山", "万梓良", "万事如意", "王老五", "万巨龙", "张三"));
+
+// Stream流 - 把集合中所有以"万"开头的元素存储到一个新的集合
+list.stream().filter(s -> s.startsWith("万")).forEach(System.out::println);
+
+// Stream流 - 把"万"开头的集合中的长度为3的元素存储到一个新的集合
+list.stream()
+        .filter(s -> s.startsWith("万"))   // 过滤"万"开头
+        .filter(s -> s.length() == 3)      // 过滤长度为 3
+        .forEach(System.out::println);     // 遍历输出
+```
+
+### Stream 流的好处
+
+**可以直接阅读代码的字面意思**,就能展示跟逻辑无关的代码语义:
+
+```
+list                          ---> 集合对象
+.stream()                     ---> 获取流
+.filter(s -> s.startsWith("万"))  ---> 过滤(过滤方式是找"万"开头)
+.forEach(System.out::println); ---> foreach 遍历(输出)
+```
+
+> 不需要再创建一个个中间集合,**一行一步操作**,链式调用非常直观。
+
+### 关键理解
+
+1. **`filter` 是过滤(筛选)操作**:传入一个判断条件(Lambda),返回符合条件的新流。
+2. **`forEach` 是遍历操作**:对流中的每个元素执行传入的动作(如打印)。
+3. **只关注"做什么"**:不用写循环变量、不用管中间集合的增删——这正是函数式编程的体现。
+4. **`Stream 流才是真正把函数式编程引入到 Java 中`**。
+
+---
+
+## 4. Stream 流的思想:三类方法(获取 / 中间 / 终结)
+
+### 概念
+
+**Stream 流**:你可以把它理解为一条**流水线**、水流——数据从一头进,经过中间一道道工序(过滤、去重、跳过……),最后从另一头产出结果。
+
+```java
+// 来源:course/Demo01.java(注释部分)
+/*
+    Stream流的思想:
+        Stream流总体来说,分为三类方法:
+            1、获取方法 -- 获取Stream流
+                |- 创建一条流水线,并且把数据放在流水线上准备进行操作。
+            2、中间方法 -- 处理Stream流
+                |- 流水线上的操作,一次操作完成后,还可以继续进行其他操作
+            3、终结方法 -- 执行最后的操作
+                |- 一个Stream流水线只能有一个终结方法,是流水线上的最后一个操作。
+*/
+```
+
+### 三类方法对比
+
+| 类别 | 作用 | 特点 |
+|------|------|------|
+| **获取方法** | 创建一条流水线,把数据放上去准备操作 | 一切的**起点** |
+| **中间方法** | 流水线上的操作(过滤、去重、跳过、截断、映射…) | 执行完**还可以继续链式调用**,如 `.filter().filter()` |
+| **终结方法** | 流水线上**最后一个**操作(遍历、计数、收集、最值…) | 一条流水线**只能有一个**终结方法,调用后流结束 |
+
+### 关键理解
+
+1. **中间方法可以连续调用多个**:如 `stream.filter(...).filter(...)`——每调用一次仍在"流水线上"。
+2. **终结方法只能有一个**:它宣告流水线的终点,调用完这条流就结束了。
+3. 常用中间方法(`filter`/`limit`/`skip`/`map`/`concat`/`distinct`)详见**第 5 节**;方法引用详见**第 6 节**;终结方法 `forEach`/`count` 详见**第 7 节**;收集方法 `collect` + `Collectors` 详见**第 8 节**。
+
+---
+
+## 5. Stream 流中间操作方法:filter / limit / skip / map / concat / distinct
+
+### 概念
+
+**中间操作方法**的意思是:执行完此方法后,**Stream 流依然可以继续执行其他操作**——中间方法往往会**返回一个新的流对象**(`Stream` 对象),就是为了让我们可以继续进行下一步操作(链式调用)。
+
+```java
+// 来源:course/Demo01.java(注释部分:中间操作方法定义)
+/*
+    Stream流中间操作方法:
+        中间操作的意思是执行完此方法后,Stream流依然可以继续执行其他操作。
+        常用的方法:
+            Stream<T> filter(Predicate<? super T> predicate)  用于对流的数据进行过滤
+            Stream<T> limit(long maxSize)   返回这个流中元素组成的流,截取前指定参数个数的数据。
+            Stream<T> skip(long n)  跳过指定参数个数的数据,返回由该流剩余元素组成的流
+            public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)    合并a和b两个流为一个流
+            Stream<T> distinct()    返回由该流的不同元素组成的流,根据Object.equals(obj)得到是否相同。
+*/
+```
+
+### 常用中间方法速查
+
+| 方法 | 作用 | 说明 |
+|------|------|------|
+| `Stream<T> filter(Predicate<? super T> predicate)` | **过滤** | 传入断言条件,返回满足条件的元素组成的新流 |
+| `Stream<T> limit(long maxSize)` | **截取** | 返回由该流中**前 `maxSize` 个**元素组成的流 |
+| `Stream<T> skip(long n)` | **跳过** | 跳过前 `n` 个元素,返回由该流**剩余元素**组成的流 |
+| `Stream<R> map(Function<? super T, ? extends R> mapper)` | **映射** | 把流中**每个元素**映射/转换成另一种类型,返回新类型组成的流 |
+| `Stream<T> concat(Stream a, Stream b)` | **合并** | 把 `a` 和 `b` **两个流合并为一个流**(静态方法) |
+| `Stream<T> distinct()` | **去重** | 返回由该流**不同元素**组成的流,根据 `Object.equals(obj)` 判断是否相同 |
+
+> `filter` 的参数类型 `Predicate<? super T>` 是 JDK 内置**函数式接口**(`java.util.function.Predicate`,有参有返回值、返回 boolean 的断言),昨天 0803 笔记中已介绍过。
+
+### 代码示例(Demo01 的 main 方法)
+
+```java
+// 来源:course/Demo01.java(main 方法:中间方法演示)
+import java.util.function.Predicate;   // 新增 import:Predicate 函数式接口
+import java.util.stream.Stream;
+
+// 承接上文生成的流(数据已换为新示例):
+Stream<String> arrayStream = Arrays.stream(new String[]{"郑伊健", "陈浩南", "齐夏", "谢文东"});
+Stream<String> stringStream = Stream.of("长贵", "赵四", "刘能", "旺财");
+
+// ① filter / limit / skip 链式调用(Predicate 匿名内部类 vs Lambda 对比在注释中演示,此处先注释,思路已明确)
+// Stream<String> newStream = stringStream.filter(s -> s.startsWith("万"));
+// Stream<String> newNewStream = newStream.limit(2);
+// newNewStream.skip(1).forEach(System.out::println);
+
+// ② concat:合并 a 和 b 两个流为一个流(Stream 接口的静态方法)
+Stream<String> concat = Stream.concat(arrayStream, stringStream);
+concat.forEach(System.out::println);
+
+// ③ distinct:去重(依据 Object.equals),重复元素只保留一个
+List<String> stringList = List.of("长贵", "赵四", "郑伊健", "陈浩南", "齐夏", "刘能",
+        "郑伊健", "陈浩南", "齐夏", "郑伊健", "陈浩南", "齐夏");
+stringList.stream().distinct().forEach(System.out::println);
+```
+
+### map 映射示例(Demo02):把学生流映射成姓名流
+
+**map**(映射)是流中非常常用的中间方法:把流中的**每个元素**转换成另一种类型/形式,返回新类型的流。典型场景——学生集合中**提取所有学生的姓名**生成一个新集合。
+
+```java
+// 来源:course/Demo02.java(main 方法)
+// 需求:把学生集合中每个学生的 name 提取出来生成一个新的集合
+// 传统写法:for 循环 + 中间集合
+ArrayList<String> nameList = new ArrayList<>();
+for (Student s : list) {
+    nameList.add(s.getName());
+}
+
+// Stream 写法:map 把"学生"映射成"姓名",再收集成 List
+List<String> nameList2 = list            // 集合对象
+        .stream()                        // 获取流
+        .map(student -> student.getName())  // 映射:获取每个学生对象的姓名
+        .collect(Collectors.toList());   // 收集成新集合
+```
+
+> map 的参数是 JDK 内置函数式接口 `Function<T, R>`(有参有返回值)——`student -> student.getName()` 就是 `Function<Student, String>` 的简化写法。
+
+### 链式调用示意(流水线视角)
+
+```
+Stream.of("万震山", "万梓良", "万事如意", "王老五")
+    .filter(姓"万")      // 中间方法 → 剩 "万震山"、"万梓良"、"万事如意"
+    .limit(2)            // 中间方法 → 截取前 2 个 → "万震山"、"万梓良"
+    .skip(1)             // 中间方法 → 跳过前 1 个 → "万梓良"
+    .forEach(...)        // 终结方法 → 只输出 "万梓良"
+```
+
+### 关键理解
+
+1. **中间方法返回新流、可无限链式**:每调用一个中间方法都返回**新的 `Stream` 对象**,所以可以 `.filter().limit().skip()` 一直写下去。
+2. **`filter` 的 Lambda 本质**:`s -> s.startsWith("万")` 就是 `Predicate<String>` 匿名内部类 `test()` 方法的简化写法(`s` 是元素,返回 boolean)。
+3. **`limit` 与 `skip` 常配合分页**:`limit(n)` 截取前 n 个;`skip(m)` 跳过前 m 个——两者组合可实现"跳过 m 条、取 n 条"的分页效果。
+4. **`concat` 是静态方法**:它属于 `Stream` 接口的**静态方法**(不是流对象方法),用法是 `Stream.concat(流a, 流b)`,把两个流合并成一个。
+5. **`distinct` 依赖 equals**:去重依据是 `Object.equals(obj)`——自定义对象要正确去重,必须重写 `equals`/`hashCode`(正好呼应前几天的 Object 类知识点)。
+6. **`map` 是"转换"而不是"筛选"**:`filter` 是"留下符合条件的",`map` 是"把每个元素变成另一种东西"(如把学生变成姓名、把 Integer 变成 String)。
+
+---
+
+## 6. 方法引用(Method Reference)—— 四种形式
+
+### 概念
+
+**方法引用**是 **Lambda 表达式的进一步简化**:当 Lambda 的方法体**只有一行代码**,且这行代码只是**调用某个已有的方法**时,就可以直接用 `::`(双冒号)引用那个方法,把参数按顺序传入。
+
+```java
+// 来源:course/Demo02.java(注释部分:方法引用四种形式)
+/*
+    方法引用:
+        引用类的静态方法:       类名::静态方法名      String::valueOf
+        引用对象的实例方法:     对象名::方法名         sb::append
+        引用类的实例方法:       类名::方法名           String::substring
+        引用类的构造方法:       类名::new              String::new
+
+        使用前提(四种通用):在重写方法的时候,方法体里面【只有一行代码】,并且这行代码是
+        调用【某个类的静态方法 / 某个对象的成员方法 / 某个类的成员方法 / 某个类的构造方法】,
+        并且我们要把重写的【抽象方法中的所有参数都按照顺序传入】到这个方法中。
+*/
+```
+
+### 四种形式速查
+
+| 形式 | 写法 | 示例 | 说明 |
+|------|------|------|------|
+| **引用类的静态方法** | `类名::静态方法名` | `String::valueOf` | 调用某个类的**静态**方法 |
+| **引用对象的实例方法** | `对象名::方法名` | `sb::append` | 调用**某个对象**的成员方法(非 static) |
+| **引用类的实例方法** | `类名::方法名` | `String::substring` | 调用**某个类**的成员方法 |
+| **引用类的构造方法** | `类名::new` | `String::new` | 调用某个类的**构造方法** |
+
+> 四种形式的共同前提:方法体只有一行代码,且抽象方法的**所有参数按顺序**传入被引用的方法中。
+
+### 代码示例一:引用类的静态方法(String::valueOf)
+
+```java
+// 来源:course/Demo02.java(main 方法:map 后把 Integer 转成 String)
+// 先 map 出每个学生的 id(Integer),再 map 用 String::valueOf 把整数转成字符串
+List<String> stringIdList = list.stream()
+        .map(s -> s.getId())            // 映射出学号(Integer 流)
+        .map(String::valueOf)           // 静态方法引用:Integer → String(相当于 new String(valueOf(元素)))
+        .collect(Collectors.toList());
+System.out.println(stringIdList);
+// 输出:[1001, 1002, 1003, ...]
+```
+
+### 代码示例二:引用对象的实例方法(sb::append)
+
+```java
+// 来源:course/Demo02.java(main 方法:把每个学生姓名追加到 StringBuilder)
+StringBuilder sb = new StringBuilder();
+list.stream()
+        .map(student -> student.getName())   // 取出姓名流
+        // 简化前——匿名内部类 Consumer
+        // .forEach(new Consumer<String>() {
+        //     public void accept(String s) { sb.append(s); }
+        // })
+        // 简化后——Lambda 表达式
+        // .forEach(s -> sb.append(s));
+        .forEach(sb::append);                // 对象方法引用:对每个元素调用 sb.append(元素)
+System.out.println(sb);
+```
+
+### 代码示例三:引用类的实例方法(String::substring —— MethodDemo 四种实现方式对比)
+
+```java
+// 来源:course/MethodDemo.java
+// 自定义函数式接口(内部接口):截取字符串
+public interface UseString {
+    String use(String str, int start, int length);
+}
+
+public static String subName(String name, UseString us) {
+    return us.use(name, 0, 1);   // 截取第 0 位开始的 1 个字符(取姓氏)
+}
+
+public static void main(String[] args) {
+    // 方式 1:传统实现类
+    UseStringImpl usi = new UseStringImpl();
+    String lastName = subName("张三", usi);              // 张
+
+    // 方式 2:匿名内部类
+    String lastName02 = subName("李四", new UseString() {
+        public String use(String str, int start, int length) {
+            return str.substring(start, length);
+        }
+    });
+
+    // 方式 3:Lambda 表达式
+    String lastName03 = subName("李四", (str, start, length) -> str.substring(start, length));
+
+    // 方式 4:引用类的实例方法 —— 类名::方法名(str.substring(start,length) 完全匹配)
+    String lastName04 = subName("王五", String::substring);
+}
+```
+
+**方式 4 为什么能成立**:重写的抽象方法 `use(String str, int start, int length)` 方法体只有一行 `str.substring(start, length)`——它调用的是**某个类(String)的成员方法**,且抽象方法的三个参数 `str, start, length` **按顺序**正好对应 `substring` 的参数,所以可以直接写成 `String::substring`。
+
+### 代码示例四:引用类的构造方法(String::new)
+
+```java
+// 来源:course/Demo02.java(main 方法:把姓名创建成 String 对象)
+list.stream()
+        .map(name -> name.getName())   // 取出姓名流
+        .map(String::new)              // 构造方法引用:等价于 new String(name)
+        .forEach(System.out::println);
+```
+
+### 关键理解
+
+1. **方法引用是 Lambda 的语法糖**:`String::valueOf` ≈ `s -> String.valueOf(s)`;`sb::append` ≈ `s -> sb.append(s)`;`String::new` ≈ `s -> new String(s)`。
+2. **本质仍是函数式接口**:方法引用替换的仍是 Lambda,底层还是函数式接口的实现——`String::substring` 实现的是 `UseString` 接口的 `use()` 方法。
+3. **使用前提是"一行代码 + 参数顺序对应"**:四种形式都必须满足"方法体只有一行代码、抽象方法的所有参数按顺序传入",否则不能使用方法引用。
+4. **区分三种实例方法引用**:`对象名::方法名`(如 `sb::append`,针对某个具体对象)与 `类名::方法名`(如 `String::substring`,第一个参数隐式作为调用对象)要分清——`类名::方法名` 形式下,抽象方法的**第一个参数**就是调用成员方法的那个对象。
+5. **与 0803 已学方法的衔接**:`Comparator.comparing(Employee::getName)` 就是"引用类的实例方法"的一种应用(`getName` 是 Employee 的成员方法)。
+
+---
+
+## 7. Stream 流终结操作方法:forEach / count
+
+### 概念
+
+**终结操作方法**的意思:执行完这个方法后,**Stream 流就不能再执行其他操作**了——一条流水线**只能有一个终结方法**,它是流水线上的最后一个操作。
+
+```java
+// 来源:course/Demo01.java(注释部分:终结操作方法定义)
+/*
+    Stream流终结操作方法:
+        终结操作的意思就是执行完这个方法后,Stream流就不能再执行其他操作了。
+        void forEach(Consumer<? super T> action);   对此流的每个元素执行操作(不一定是输出)
+        long count();   返回此流中的元素数
+*/
+```
+
+### 常用终结方法速查
+
+| 方法 | 作用 | 说明 |
+|------|------|------|
+| `void forEach(Consumer<? super T> action)` | **遍历** | 对流的每个元素执行操作(不一定是输出,也可以做其他事) |
+| `long count()` | **计数** | 返回此流中的元素个数(注意返回值是 `long`) |
+
+> `forEach` 的参数 `Consumer<? super T>` 是 JDK 内置**函数式接口**(`java.util.function.Consumer`,有参无返回值)——昨天 0803 笔记中介绍过。
+
+### 关键理解
+
+1. **终结方法 = 流水线终点**:调用 `forEach`/`count` 后,这条流就结束了,不能再继续 `.filter()` 等操作。
+2. **`forEach` 不一定只是输出**:它的 `Consumer` 参数可以执行任意动作(打印、计数、写入集合等),`System.out::println` 只是最常见的一种(方法引用)。
+3. **`count` 返回 `long`**:元素个数用 `long` 表示,配合中间方法可统计"过滤后还剩多少个"。
+
+---
+
+## 8. Stream 流收集操作方法:collect + Collectors(toList / toSet / toMap)
+
+### 概念
+
+**收集操作方法**:当我们使用流对一些数据操作完毕后,**可以把流中的数据收集到集合**(List / Set / Map / 数组)中——`collect` 也是终结操作的一种,但它把结果"存起来"而非直接输出。
+
+```java
+// 来源:course/Demo01.java(注释部分:收集操作方法定义)
+/*
+    Stream流收集操作方法:
+        当我们使用流对一些数据操作完毕后,可以把流中的数据收集到集合中。
+        <R, A> R collect(Collector<? super T, A, R> collector);     把结果收集到集合中
+        Object[] toArray(); 把结果收集到数组,生成一个新的结果集数组
+        default List<T> toList()    把结果收集到List集合,生成一个新的List集合,这里返回的List集合是不可变的集合。
+    工具类Collectors提供了具体的收集方式:
+        toMap(Function keyMapper, Function valueMapper)    把元素收集到map集合
+        toList()    把元素收集到List集合
+        toSet()     把元素收集到Set集合
+*/
+```
+
+### 收集方式速查
+
+| 方法 | 收集目标 | 说明 |
+|------|----------|------|
+| `collect(Collector collector)` | 集合 | 配合 `Collectors` 工具类的具体收集器使用 |
+| `toArray()` | 数组 | 把结果收集成新数组 |
+| `toList()` | List | 把结果收集成新 List——**返回的 List 是不可变集合**(不能增删改) |
+| `Collectors.toList()` | List | 工具类 Collectors 提供的收集方式 |
+| `Collectors.toSet()` | Set | 工具类 Collectors 提供的收集方式(Set 天然去重) |
+| `Collectors.toMap(keyMapper, valueMapper)` | Map | 工具类 Collectors 提供的收集方式,两个参数都是 `Function` 函数式接口 |
+
+### 代码示例一:Demo01 —— collect 收集成 List / Set
+
+```java
+// 来源:course/Demo01.java(main 方法:收集操作演示)
+import java.util.stream.Collectors;   // 新增 import:Collectors 收集工具类
+
+List<String> stringList = List.of("长贵", "赵四", "郑伊健", "陈浩南", "齐夏", "刘能",
+        "郑伊健", "陈浩南", "齐夏", "郑伊健", "陈浩南", "齐夏");
+
+// ① 去重后收集成 List 集合
+List<String> list1 = stringList.stream()   // 集合对象
+        .distinct()                        // 去重
+        .collect(Collectors.toList());     // 收集成 List
+System.out.println(list1);
+
+// ② 过滤"伊"后收集成 Set 集合(Set 天然去重,即使不过滤 distinct 也只有不重复的)
+Set<String> set = stringList.stream()            // 集合对象
+        .filter(s -> s.contains("伊"))           // 过滤包含"伊"的
+        .collect(Collectors.toSet());            // 收集成 Set
+System.out.println(set);
+```
+
+### 代码示例二:ToMapDemo —— collect 收集成 Map(重点)
+
+新增两个文件:`Student` 实体类 + `ToMapDemo` 演示类。
+
+```java
+// 来源:course/Student.java(实体类,作为 toMap 的值对象)
+public class Student {
+    private Integer id;      // 学号
+    private String name;     // 姓名
+    // 无参构造 / 有参构造(id, name)
+    // getter / setter
+    // equals / hashCode:基于 id + name(Objects.equals / Objects.hash)
+    // toString()
+}
+```
+
+```java
+// 来源:course/ToMapDemo.java(main 方法)
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+// 创建 7 名学生放入 ArrayList
+Student s1 = new Student(1001, "项羽");
+Student s2 = new Student(1002, "张良");
+// ... s3~s7 略(项庄/刘邦/萧何/韩信/樊哙)
+ArrayList<Student> list = new ArrayList<>(List.of(s1, s2, s3, s4, s5, s6, s7));
+
+/*
+    Collectors.toMap 的完整签名:
+    public static <T, K, U> Collector<T, ?, Map<K, U>> toMap(
+        Function<? super T, ? extends K> keyMapper,   // 取"键"的函数
+        Function<? super T, ? extends U> valueMapper  // 取"值"的函数
+    )
+*/
+
+// 方式一:Lambda 写法 —— toMap(键, 值)
+Map<Integer, Student> map = list                 // 集合对象
+        .stream()                                // 生成流
+        .collect(Collectors.toMap(
+                student -> student.getId(),      // 键:学号 id
+                student -> student               // 值:学生对象本身
+        ));
+System.out.println(map);
+
+// 方式二:匿名内部类写法(Function 函数式接口,与 Lambda 等价)
+Map<Integer, Student> map1 = list.stream()
+        .collect(Collectors.toMap(
+                new Function<Student, Integer>() {            // keyMapper
+                    @Override
+                    public Integer apply(Student student) {
+                        return student.getId();
+                    }
+                },
+                new Function<Student, Student>() {            // valueMapper
+                    @Override
+                    public Student apply(Student student) {
+                        return student;
+                    }
+                }
+        ));
+```
+
+### 关键理解
+
+1. **`collect` 是终结操作**:调用完流就结束,但结果不是直接输出,而是**收集到集合/数组**,方便后续继续使用。
+2. **`Collectors` 是收集工具类**:类比 `Arrays`(数组工具类)、`Collections`(集合工具类),`Collectors` 专门为流提供收集器——`toList()` / `toSet()` / `toMap()`。
+3. **`toMap` 的两个参数都是 `Function`**:`Function<T, R>` 是 JDK 内置函数式接口(有参有返回值)——第一个函数从元素中取**键**(keyMapper),第二个函数从元素中取**值**(valueMapper)。`student -> student.getId()` 取学号当键,`student -> student` 取学生对象当值。
+4. **Lambda vs 匿名内部类**:`student -> student.getId()` 就是 `new Function<Student, Integer>(){...}` 的简化写法,两种方式等价,Lambda 更简洁(对应 0803 笔记的省略规则)。
+5. **`toList()` 与 `Stream.toList()` 返回不可变集合**:收集出的 List 不能 `add`/`remove`(呼应 `List.of()` 不可变集合的坑)。
+6. **`toSet()` 天然去重**:收集成 Set 本身就保证元素不重复。
+7. **`toMap` 键冲突会抛异常**:如果多个元素映射出相同的键,`Collectors.toMap` 默认抛 `IllegalStateException`——所以键必须是唯一的(如学号 id)。
+8. **贪心思想注释**:ToMapDemo 注释中提到的"贪心思想——局部最优解,不代表全局最优解",是程序设计中的一个思维比喻,提醒我们局部最优不等于全局最优。
+
+---
+
+## 9. 生成 Stream 流的四种方式
+
+### 数据来源对应四种生成方式
+
+| 数据来源 | 生成方式 | 说明 |
+|----------|----------|------|
+| **Collection 体系集合** | `集合对象.stream()` | 使用默认方法 `default Stream<E> stream()` |
+| **Map 体系集合** | `map.keySet().stream()` / `map.values().stream()` | 把 Map 转换成 **Set 集合**,间接生成流 |
+| **数组** | `Arrays.stream(数组)` | `Arrays` 静态方法 `public static IntStream stream(int[] array)` |
+| **相同类型的多个数据** | `Stream.of(T... value)` | `Stream` 接口的静态方法 `of(T...value)` |
+
+### 代码示例(Demo01 的 main 方法)
+
+```java
+// 来源:course/Demo01.java
+package course;
+
+import java.util.*;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+public class Demo01 {
+    public static void main(String[] args) {
+        // 1. Collection体系集合:使用默认方法 stream() 生成流
+        List<String> list = new ArrayList<>();
+        Stream<String> stream = list.stream();
+
+        // 2. Map体系集合:把 Map 转换成 Set,间接的生成流
+        Map<String, Object> map = new HashMap<>();
+        Stream<String> keyStream = map.keySet().stream();   // key 就是 Set 集合
+        Stream<Object> valueStream = map.values().stream(); // value 就是 List 集合
+
+        // 3. 数组:通过 Arrays 中的静态方法 stream 生成流
+        String[] array = {"郑伊健", "陈浩南", "齐夏", "谢文东"};
+        Stream<String> arrayStream = Arrays.stream(array);
+
+        // 4. 相同类型的多个数据:通过 Stream 接口的静态方法 of(T...value) 生成流
+        Stream<String> stringStream = Stream.of("长贵", "赵四", "刘能", "旺财");
+        Stream<Integer> integerStream = Stream.of(10, 15, 20, 35, 46);
+    }
+}
+```
+
+### 关键理解
+
+1. **`stream()` 是 Collection 接口的默认方法**:`List` / `Set` / `Queue` 这些 Collection 体系集合都能直接调用。
+2. **Map 不能直接生成流**:因为 Map 不是 Collection 体系,需要先把 `keySet()`(Set)或 `values()`(Collection)取出来再 `.stream()`。
+3. **`Arrays.stream()` 专门处理数组**:数组不是集合,不能调 `.stream()`,要用 `Arrays` 工具类的静态方法。
+4. **`Stream.of(T... values)` 处理零散数据**:注意 `of` 的参数是**可变参数**(`T...`)——正好用上了昨天学的 Varargs!`Stream.of(10, 15, 20, 35, 46)` 本质就是 `Stream.of(new Integer[]{10,15,20,35,46})`。
+
+---
+
+## 10. 20260803 课后作业回顾:Lambda 表达式应用(Addable / Flyable)
+
+### 作业场景回顾
+
+调用某个方法的时候,需要做哪些事情?核心思路:
+
+1. **先看所属的类**
+2. **再看修饰符**(访问权限修饰符、静态修饰符)
+   - 如果是 `private` 修饰符 → 肯定调用不了
+   - 如果是 `static` 修饰 → 类名.方法名() 直接调用;否则创建对象用对象调用
+3. **看方法的参数**:只要不是那 8 个基本数据类型,其他的都是**引用数据类型变量**,都需要传入**对象**
+   - 传入的对象必须是该参数类型的,或者是该参数类型的**子类、实现类的对象**——这就形成了**多态**
+
+```java
+// 来源:homework0803/p4_lambda/LambdaDemo.java(注释部分)
+// 只要不是那8个基本数据类型,其他的都是引用数据类型变量。都需要在调用方法的时候传入 【对象】
+// 传入的对象必须是该参数类型的,或者是该参数类型的子类、实现类的对象。 这就形成了多态
+```
+
+### 函数式接口定义
+
+```java
+// 来源:homework0803/p4_lambda/Addable.java
+// 有参有返回值的函数式接口
+package homework0803.p4_lambda;
+
+public interface Addable {
+    int add(int x, int y);
+}
+```
+
+```java
+// 来源:homework0803/p4_lambda/Flyable.java
+// 有参无返回值的函数式接口
+package homework0803.p4_lambda;
+
+public interface Flyable {
+    void fly(String s);
+}
+```
+
+### Lambda 传参调用
+
+```java
+// 来源:homework0803/p4_lambda/LambdaDemo.java
+package homework0803.p4_lambda;
+
+public class LambdaDemo {
+
+    public static void useAdd(Addable a) {
+        System.out.println(a.add(10, 20));
+    }
+
+    public static void useFlyable(Flyable f) {
+        f.fly("今天天气不错...是风和日丽的...");
+    }
+
+    public static void main(String[] args) {
+        // 调用 useAdd 方法,传入参数有两种选择:
+        //   1、自己新建一个实现类,实现接口,重写方法。
+        //   2、创建该接口的匿名内部类对象。
+        // 现在还有第 3 种:直接传入 Lambda 表达式(本质是简化后的匿名内部类)
+        useAdd((x, y) -> x + y);   // 有参有返回值:单语句 → 省略大括号、分号、return
+    }
+}
+```
+
+### 知识点总结
+
+| 知识点 | 说明 |
+|--------|------|
+| **调用方法三步看** | ①所属类 ②修饰符(private 不能调 / static 类名调 / 否则对象调)③参数类型 |
+| **引用类型参数传对象** | 参数不是 8 大基本类型时,就要传**该类型或其子类/实现类的对象**(多态) |
+| **Lambda 是第三种传参方式** | 方法参数是函数式接口时,可传实现类对象、匿名内部类对象,或直接传 Lambda |
+| **`useAdd((x, y) -> x + y)`** | 两个参数(小括号不能省),方法体只有一条表达式语句(省大括号/分号/return) |
+
+---
+
+## 11. 20260803 课后作业回顾:Employee 员工管理(TreeSet 自然排序 + Comparator 按姓名 + 方法引用)
+
+### 作业要求回顾
+
+> 创建 `TreeSet<Employee>`(自然排序),添加 5 名员工(其中**两名工号相同**,测试去重):按薪资降序、薪资相同再按工号;再用迭代器遍历输出。最后**通讯录按姓名排序**:重新创建 TreeSet 传入按姓名排序的 Comparator。
+
+### Employee 实体类:实现 Comparable + equals/hashCode
+
+```java
+// 来源:homework0803/p5_employee/Employee.java
+package homework0803.p5_employee;
+
+import java.util.Objects;
+
+public class Employee implements Comparable<Employee> {
+    private String id;      // 工号
+    private String name;    // 姓名
+    private double salary;  // 薪资
+    private String dept;    // 部门
+
+    public Employee(String id, String name, double salary, String dept) {
+        this.id = id;
+        this.name = name;
+        this.salary = salary;
+        this.dept = dept;
+    }
+
+    // getter / setter ...(略)
+
+    // 自然排序:按薪资降序,薪资相同再按工号
+    @Override
+    public int compareTo(Employee o) {
+        int result = Double.compare(o.getSalary(), this.salary);  // 降序:交换参数
+        return result == 0 ? this.id.compareTo(o.getId()) : result;  // 薪资相同按工号
+    }
+
+    // equals / hashCode:以工号判定(与 compareTo 的"工号"判定口径一致)
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof Employee e) {
+            return Objects.equals(id, e.id);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id);
+    }
+}
+```
+
+### EmployeeSystem:TreeSet 自然排序 + 迭代器遍历
+
+```java
+// 来源:homework0803/p5_employee/EmployeeSystem.java(部分)
+package homework0803.p5_employee;
+
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.TreeSet;
+
+public class EmployeeSystem {
+    public static void main(String[] args) {
+        // 1. 创建 TreeSet<Employee>(自然排序:按薪资降序,薪资相同按工号;工号重复自动去重)
+        TreeSet<Employee> treeSet = new TreeSet<>();
+        treeSet.add(new Employee("E001", "张三", 12000, "技术部"));
+        treeSet.add(new Employee("E002", "李四", 15000, "市场部"));
+        treeSet.add(new Employee("E003", "王五", 15000, "技术部"));
+        treeSet.add(new Employee("E004", "赵六", 9000,  "财务部"));
+        treeSet.add(new Employee("E005", "刘七", 12000, "市场部"));
+        treeSet.add(new Employee("E002", "李四", 20000, "市场部"));  // 工号重复 → 被去重
+
+        // 2. 迭代器遍历输出 → 观察:按薪资降序,薪资相同按工号;工号重复的自动去重
+        Iterator<Employee> iterator = treeSet.iterator();
+        while (iterator.hasNext()) {
+            Employee e = iterator.next();
+            System.out.println(e.getId() + "-" + e.getName() + "-" + e.getSalary() + "-" + e.getDept());
+        }
+
+        // 3. 通讯录按姓名排序:重新创建 TreeSet,传入按姓名排序的 Comparator(此处用方法引用)
+        treeSet = new TreeSet<>(Comparator.comparing(Employee::getName));
+        // 再添加 6 名员工(同上),迭代器遍历输出 → 按姓名排序
+    }
+}
+```
+
+### 方法引用(Method Reference)—— 新知识
+
+> 方法引用的**四种形式**(静态方法 / 对象实例方法 / 类实例方法 / 构造方法)详见**第 6 节**,本节是它在 Comparator 中的一次具体应用。
+
+`Comparator.comparing(Employee::getName)` 里的 `Employee::getName` 就是**方法引用**——它是 Lambda 的一种**更简化的写法**:
+
+| 写法 | 代码 | 说明 |
+|------|------|------|
+| **匿名内部类** | `new Comparator<Employee>() { compare(...) { return o1.getName().compareTo(o2.getName()); } }` | 最原始,最啰嗦 |
+| **Lambda 表达式** | `(o1, o2) -> o1.getName().compareTo(o2.getName())` | 省略类型、大括号、return |
+| **方法引用** | `Comparator.comparing(Employee::getName)` | 用 `类名::方法名` 直接引用现有方法,最简洁 |
+
+```java
+// 来源:homework0803/p5_employee/EmployeeSystem.java(注释提示)
+// 可用匿名内部类,也可尝试用 Lambda:Comparator.comparing(Employee::getName) 或
+// (o1, o2) -> o1.getName().compareTo(o2.getName())
+```
+
+### 知识点总结
+
+| 知识点 | 说明 |
+|--------|------|
+| **自然排序 + 二次比较** | `compareTo` 先按薪资(`Double.compare`),薪资相同返回 0 时再按工号比较,保证唯一 |
+| **降序技巧** | 交换 `compare` 参数位置:`Double.compare(o.getSalary(), this.salary)` = 薪资降序 |
+| **compareTo / equals / hashCode 一致性** | 都以**工号**判定口径 → TreeSet 去重(E002 重复被丢弃)与排序行为一致,符合官方推荐 |
+| **`instanceof` 模式匹配** | `if (obj instanceof Employee e)` 直接得到强转后的变量 `e`(JDK16+ 特性) |
+| **`Comparator.comparing(类::getter)`** | 方法引用写法,`TreeSet<>(Comparator.comparing(Employee::getName))` = 按姓名排序 |
+| **方法引用 vs Lambda** | 方法引用是 Lambda 的进一步简化:`Employee::getName` ≈ `(e) -> e.getName()` |
+
+---
+
+## 12. 随堂练习要点
+
+### 练习一:Stream 流筛选集合元素
+
+**核心步骤**:
+1. 创建 `ArrayList<String>`,存入"万震山、万梓良、万事如意、王老五、万巨龙、张三"
+2. 用 `list.stream().filter(s -> s.startsWith("万")).forEach(...)` 筛选并遍历"万"开头
+3. 再用 `.filter(s -> s.length() == 3)` 叠加筛选长度为 3 的元素
+
+**思考**:
+1. 传统 for 循环要写几个集合?Stream 流为什么不用中间集合?—— 传统写法每筛选一次要建一个新集合;Stream 用 `filter` 链式过滤,数据在流水线上流动,不需要中间容器
+2. `.filter()` 属于三类方法中的哪一类?—— 中间方法(可连续调用多个);`.forEach()` 呢?—— 终结方法(只能有一个,流水线终点)
+
+### 练习二:四种方式生成 Stream 流
+
+**核心步骤**:
+1. Collection 集合:`list.stream()`
+2. Map 集合:`map.keySet().stream()` / `map.values().stream()`
+3. 数组:`Arrays.stream(array)`
+4. 零散数据:`Stream.of(10, 15, 20, 35, 46)`
+
+**思考**:
+1. Map 为什么不能直接 `.stream()`?—— Map 不是 Collection 体系,要先 `keySet()` 或 `values()` 取出 Set/Collection
+2. `Stream.of(T... values)` 用了昨天学的什么语法?—— 可变参数 Varargs(`T...`),所以可以传任意个数数据
+
+### 练习三:函数式接口 + Lambda 传参(作业一)
+
+**核心步骤**:
+1. 定义有参有返回值的函数式接口 `Addable`(`int add(int x, int y)`)
+2. 定义有参无返回值的函数式接口 `Flyable`(`void fly(String s)`)
+3. 定义 `useAdd(Addable a)` / `useFlyable(Flyable f)` 方法,调用时直接传 Lambda
+4. 用 `useAdd((x, y) -> x + y)` 体会单语句省略大括号/分号/return
+
+**思考**:
+1. 方法参数是函数式接口时,能传哪几种东西?—— 实现类对象、匿名内部类对象、Lambda 表达式(三种都能体现多态)
+2. 调用方法前"三步看"是什么?—— 看所属类 → 看修饰符(private/static)→ 看参数类型(引用类型要传对象)
+
+### 练习四:Employee 员工排序(作业二)
+
+**核心步骤**:
+1. `Employee` 实现 `Comparable<Employee>`,`compareTo` 按薪资降序、薪资相同按工号
+2. `equals` / `hashCode` 以工号判定(与 compareTo 口径一致)
+3. `TreeSet<Employee>` 存入 6 名员工(含工号重复的 E002),迭代器遍历观察**排序 + 去重**
+4. 重新 `new TreeSet<>(Comparator.comparing(Employee::getName))` 按姓名排序,迭代器遍历
+
+**思考**:
+1. E001(12000)和 E005(12000)薪资相同为什么都能存入?—— compareTo 薪资相同后继续比较工号(E001 ≠ E005),返回值不为 0,不视为重复
+2. 工号重复的 E002 为什么被去重?—— compareTo 判定重复(0)且 equals/hashCode 也按工号判定,TreeSet 去重规则生效
+3. `Comparator.comparing(Employee::getName)` 中的 `Employee::getName` 是什么写法?—— 方法引用,等价于 Lambda `(e) -> e.getName()`
+4. 方法引用比 Lambda 省了什么?—— 省掉参数列表和箭头,直接用 `类名::方法名` 引用现有方法(当前作业已用上)
+
+### 练习五:Stream 流中间操作方法(filter / limit / skip / concat / distinct)
+
+**核心步骤**:
+1. `Stream.of("万震山", "万梓良", "万事如意", "王老五")` 生成流
+2. `.filter(s -> s.startsWith("万"))` 过滤出"万"开头 → 再 `.limit(2)` 截取前 2 个 → 再 `.skip(1)` 跳过前 1 个 → `.forEach(System.out::println)` 输出
+3. 体会"每个中间方法都返回**新的流对象**",所以能一直链式调用
+4. 尝试 `Stream.concat(流a, 流b)` 合并两个流;给包含重复元素的数据加 `.distinct()` 去重
+
+**思考**:
+1. 中间方法和终结方法怎么区分?—— 中间方法执行完返回**新的 Stream**,还能继续链式操作;终结方法执行完流就结束,不能再调用其他方法
+2. `filter` 的 Lambda `s -> s.startsWith("万")` 本质是什么?—— 是 `Predicate<String>` 函数式接口 `test()` 方法的简化(有参、返回 boolean 的断言)
+3. `limit` 和 `skip` 组合能实现什么?—— 分页:`skip(m)` 跳过前 m 条、`limit(n)` 取 n 条
+4. `distinct` 去重的依据是什么?—— `Object.equals(obj)`,自定义对象要重写 `equals`/`hashCode` 才能正确去重
+
+### 练习六:Stream 流终结操作方法(forEach / count)
+
+**核心步骤**:
+1. `list.stream().forEach(System.out::println)` 遍历输出每个元素
+2. `long count = list.stream().filter(s -> s.contains("伊")).count();` 统计过滤后元素个数
+3. 尝试在 `forEach` 之后继续调用 `.filter(...)`,观察编译/运行结果
+
+**思考**:
+1. 为什么 `forEach` 之后不能再调用其他方法?—— `forEach` 是终结操作,调用后流就结束了,不能继续链式操作
+2. `forEach` 的参数 `Consumer` 一定只能打印吗?—— 不一定,`Consumer<T>` 是有参无返回值的函数式接口,可以做任意动作(打印、计数、写入等)
+3. `count()` 的返回值类型是什么?—— `long`,不是 `int`
+
+### 练习七:Stream 流收集操作(collect / Collectors:toList / toSet / toMap)
+
+**核心步骤**:
+1. `list.stream().distinct().collect(Collectors.toList())` 去重后收集成 List,打印观察
+2. `list.stream().filter(...).collect(Collectors.toSet())` 过滤后收集成 Set,观察 Set 天然去重
+3. 定义 `Student` 实体类(id、name、getter/setter、equals/hashCode、toString),创建 7 名学生
+4. `list.stream().collect(Collectors.toMap(s -> s.getId(), s -> s))` 收集成 `Map<Integer, Student>`,键是学号、值是学生对象
+5. 尝试把 `s -> s.getId()` 改写成匿名内部类 `new Function<Student, Integer>() {...}`,对比两种写法
+
+**思考**:
+1. `collect` 是中间操作还是终结操作?—— 终结操作(执行完流结束),但结果不是直接输出,而是**收集到集合/数组**里
+2. `Collectors.toMap` 的两个参数分别是什么?—— 第一个 `keyMapper` 从元素中取**键**,第二个 `valueMapper` 从元素中取**值**,都是 `Function` 函数式接口
+3. 为什么用学号当键?—— 键必须唯一;如果两个元素映射出相同键,`toMap` 默认抛 `IllegalStateException`
+4. `collect(Collectors.toList())` 得到的 List 能 `add` 吗?—— 不能,`Collectors.toList()` / `Stream.toList()` 返回的是**不可变集合**,直接增删改抛 `UnsupportedOperationException`
+5. Lambda `s -> s.getId()` 与匿名内部类 `new Function<Student, Integer>(){...}` 是什么关系?—— 等价,Lambda 是后者的简化写法
+
+### 随堂练习参考解答(已提供代码)
+
+对应今日随堂练习的参考解答已写入授课代码:
+
+| 练习 | 参考代码路径 |
+|------|-------------|
+| 练习 3 分页:limit/skip 分页 + `Stream.toList()` | [`exericse/exericse03/PageTest.java`](./../授课代码/c260804/src/exericse/exericse03/PageTest.java) |
+
+**PageTest 参考解答要点**:
+
+```java
+// 来源:exericse/exericse03/PageTest.java
+// 1. limit(5):截取前 5 个
+Stream.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9).limit(5).forEach(i -> System.out.print(i + " "));
+
+// 2. skip(3):跳过前 3 个
+Stream.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9).skip(3).forEach(i -> System.out.print(i + " "));
+
+// 3. 用 Stream.toList() 把流收集成 List(注意:返回不可变集合)
+List<Integer> list = Stream.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9).toList();
+
+// 分页方法:skip((pageNo-1) * pageSize).limit(pageSize)
+public static void showPage(List<Integer> list, int pageNo, int pageSize) {
+    list.stream()
+        .skip((pageNo - 1) * pageSize)   // 跳过前面的页
+        .limit(pageSize)                 // 截取本页数量
+        .forEach(i -> System.out.print(i + " "));
+}
+showPage(list, 1, 3);   // 0 1 2
+showPage(list, 2, 3);   // 3 4 5
+showPage(list, 3, 3);   // 6 7 8
+showPage(list, 4, 3);   // 9
+```
+
+---
+
+## 13. 拓展阅读
+
+### 官方文档
+
+- [函数式编程概念(维基百科)](https://en.wikipedia.org/wiki/Functional_programming)
+- [Java 8 Lambda 表达式(Oracle 教程)](https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html)
+- [Java 8 方法引用(Oracle 教程)](https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html)
+- [java.util.stream 包 (JDK 17)](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/stream/package-summary.html)
+- [Stream 接口 (JDK 17)](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/stream/Stream.html)
+- [Stream.of() (JDK 17)](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/stream/Stream.html#of(T...))
+- [Arrays.stream() (JDK 17)](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Arrays.html#stream(T[]))
+- [Comparator.comparing() (JDK 17)](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Comparator.html#comparing(java.util.function.Function))
+- [Collectors 工具类 (JDK 17)](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/stream/Collectors.html)
+- [Collector 接口 (JDK 17)](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/stream/Collector.html)
+- [Consumer 函数式接口 (JDK 17)](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/function/Consumer.html)
+- [Function 函数式接口 (JDK 17)](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/function/Function.html)
+- [Stream.map() (JDK 17)](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/stream/Stream.html#map(java.util.function.Function))
+- [Stream.toList() (JDK 17)](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/stream/Stream.html#toList())
+
+### 推荐阅读
+
+- 《Java 8 实战》第 4 章 引入流、第 5 章 使用流 —— Stream 流的入门与常用操作
+- 《Java 8 实战》第 3 章 Lambda 表达式 —— 方法引用、复合 Lambda
+- 《Effective Java》第 3 版 第 42~44 条:Lambda 优先于匿名类、方法引用优先于 Lambda
+- 《Java 核心技术 卷 I》第 6 章 接口、lambda 表达式与内部类
+- 《Java 8 实战》第 3 章 3.3 节 —— 方法引用:四种形式与使用场景
+
+### 相关知识点
+
+| 前置知识 | 当前知识 | 后续知识 |
+|----------|----------|----------|
+| 匿名内部类(0727) | 函数式编程思想 | 方法引用、Stream 高阶函数 |
+| Lambda 表达式(0803) | Lambda 应用(Addable/Flyable) | Stream API 的 filter/map/collect |
+| Comparable/Comparator(0803) | TreeSet 员工排序 + 方法引用 | Stream 流的 sorted 排序、Collectors |
+| 可变参数 Varargs(0803) | `Stream.of(T... values)` 可变参数应用 | 方法引用 & 收集器 |
+| Collection 体系集合(0729) | 生成 Stream 流的四种方式 | 中间方法(filter/limit/skip/distinct/map) |
+| 函数式接口 Predicate(0803) | 中间操作方法(filter/limit/skip/concat/distinct) | Stream 映射 map、终结方法 collect/count、Collectors 收集器 |
+| 函数式接口 Consumer/Function(0803) | 终结操作 forEach/count、收集操作 collect/Collectors(toList/toSet/toMap) | Collectors 高级用法(groupingBy 分组、joining 拼接)、Stream 与集合互转 |
+| Lambda 表达式(0803) | 方法引用四种形式(静态/对象实例/类实例/构造) | 方法引用的高级组合、Stream 中方法引用大量应用 |

+ 662 - 0
课堂练习题/20260804-Stream流练习.md

@@ -0,0 +1,662 @@
+# 2026-08-04 Stream 流与函数式编程专项练习(课堂练习用)
+
+> **说明**:以下习题围绕今日授课核心内容——**函数式编程与 Stream 流** 展开,涵盖 Stream 流的四种生成方式(Collection.stream() / Map 转 Set / Arrays.stream() / Stream.of())、中间操作方法(filter / limit / skip / concat / distinct)、终结操作方法(forEach / count)、收集操作方法(collect + Collectors.toList / toSet / toMap)、Stream 链式调用与分页思想,以及 Stream 中 Lambda 表达式(Predicate / Consumer / Function 函数式接口)与方法引用的应用等知识点。
+>
+> 题目分为 **基础练习**(生成流 / 中间操作 / 终结操作 / 收集操作,6 题)和 **进阶挑战**(Collectors.toMap 收集成 Map / 综合实战,2 题)。
+
+---
+
+# 第一部分:基础练习
+
+> 以下练习围绕今日授课核心内容展开,由浅入深。
+
+---
+
+## 练习 1:生成 Stream 流的四种方式
+
+**难度**:⭐
+**知识点**:`stream()` 默认方法、`Map keySet()/values()`、`Arrays.stream()`、`Stream.of(T...values)`、可变参数
+
+**场景描述**:Stream 流操作的第一步是**获取流**(创建一条流水线,把数据放上去)。不是所有数据都能直接调 `.stream()`——Collection 体系集合可以用默认方法 `stream()`;Map 要转成 Set 间接生成;数组要用 `Arrays.stream()`;零散数据用 `Stream.of()`。
+
+**题目要求**:
+
+```java
+import java.util.*;
+import java.util.stream.Stream;
+
+public class StreamCreateTest {
+    public static void main(String[] args) {
+        // 1. Collection 体系集合:使用默认方法 stream() 生成流
+        List<String> list = new ArrayList<>(List.of("张三", "李四", "王五"));
+        Stream<String> stream = list.stream();   // 集合.stream()
+
+        // 2. Map 体系集合:把 Map 转换成 Set,间接生成流
+        Map<String, Object> map = new HashMap<>();
+        // TODO: 用 keySet() 取键集合生成流,赋给 keyStream
+        //       用 values() 取值集合生成流,赋给 valueStream
+
+        // 3. 数组:通过 Arrays 中的静态方法 stream 生成流
+        String[] array = {"郑伊健", "陈浩南", "齐夏", "谢文东"};
+        // TODO: Arrays.stream(array) 生成流,赋给 arrayStream
+
+        // 4. 相同类型的多个数据:Stream 接口的静态方法 of(T...values)
+        // TODO: 1) Stream<String> stringStream = Stream.of("长贵", "赵四", "刘能", "旺财");
+        //       2) Stream<Integer> integerStream = Stream.of(10, 15, 20, 35, 46);
+    }
+}
+```
+
+**思考题**:
+1. Map 为什么不能直接调用 `.stream()`?(提示:Map 不属于 Collection 体系)
+2. `Stream.of(10, 15, 20, 35, 46)` 用到了之前学的什么语法?(提示:可变参数 `T...`)
+3. `Arrays.stream(array)` 和 `list.stream()` 有什么区别?为什么数组不能调 `.stream()`?
+
+---
+
+## 练习 2:filter 过滤 + forEach 遍历(筛选集合元素)
+
+**难度**:⭐⭐
+**知识点**:`filter(Predicate)`、`forEach(Consumer)`、Lambda 表达式、`startsWith()/length()` 字符串方法
+
+**场景描述**:Stream 流中最常用的操作——**过滤**(filter)和**遍历**(forEach)。今天课上的案例:从集合中筛选出所有以"万"开头的元素,再筛选出其中长度为 3 的元素。
+
+**题目要求**:
+
+```java
+import java.util.ArrayList;
+import java.util.List;
+
+public class FilterTest {
+    public static void main(String[] args) {
+        List<String> list = new ArrayList<>(List.of("万震山", "万梓良", "万事如意", "王老五", "万巨龙", "张三"));
+
+        // 1. 用 Stream 流筛选出所有以"万"开头的元素,并遍历输出
+        //    TODO: list.stream().filter(...).forEach(System.out::println);
+        //          过滤条件:s -> s.startsWith("万")
+
+        // 2. 再叠加一次过滤:以"万"开头 且 长度为 3 的元素,遍历输出
+        //    TODO: 连续调用两次 filter
+
+        // 3. 挑战:用 count() 统计"万"开头且长度为 3 的元素个数
+        //    TODO: long count = list.stream()...count();  System.out.println(count);
+    }
+}
+```
+
+**预期输出**:
+```
+========== 以"万"开头 ==========
+万震山
+万梓良
+万事如意
+万巨龙
+========== 以"万"开头 且 长度为 3 ==========
+万震山
+万梓良
+万巨龙
+========== 个数统计 ==========
+3
+```
+
+**思考题**:
+1. `.filter()` 的参数 `s -> s.startsWith("万")` 本质是什么?(提示:`Predicate<String>` 函数式接口 `test()` 方法的简化)
+2. 为什么可以连续写两个 `.filter()`?(提示:中间方法返回新的流对象,可链式调用)
+3. `.forEach(System.out::println)` 用到了什么写法?(提示:方法引用,等价于 `s -> System.out.println(s)`)
+
+---
+
+## 练习 3:中间方法链式调用——filter / limit / skip(分页)
+
+**难度**:⭐⭐
+**知识点**:`filter`、`limit(long maxSize)`、`skip(long n)`、链式调用、分页思想
+
+**场景描述**:中间方法执行完**返回新的流对象**,可以一直链式调用。`limit(n)` 截取前 n 个元素,`skip(m)` 跳过前 m 个元素——两者组合可以轻松实现**分页**功能(跳过 m 条、取 n 条)。
+
+**题目要求**:
+
+```java
+import java.util.stream.Stream;
+
+public class PageTest {
+    public static void main(String[] args) {
+        // 1. 生成长度 0~9 的整数流,截取前 5 个(limit(5)),遍历输出 → 期望 0 1 2 3 4
+        //    TODO: Stream.of(0,1,2,3,4,5,6,7,8,9).limit(5).forEach(...)
+
+        // 2. 生成长度 0~9 的整数流,跳过前 3 个(skip(3)),遍历输出 → 期望 3 4 5 6 7 8 9
+
+        // 3. 分页:集合共 10 条数据,每页 3 条
+        //    第 1 页:skip(0).limit(3)  → 0 1 2
+        //    第 2 页:skip(3).limit(3)  → 3 4 5
+        //    第 3 页:skip(6).limit(3)  → 6 7 8
+        //    第 4 页:skip(9).limit(3)  → 9
+        //    TODO: 封装一个方法 showPage(List<Integer> list, int pageNo, int pageSize)
+        //          公式:skip((pageNo-1) * pageSize).limit(pageSize)
+    }
+}
+```
+
+**预期输出**:
+```
+========== limit(5) 截取前 5 个 ==========
+0 1 2 3 4
+========== skip(3) 跳过前 3 个 ==========
+3 4 5 6 7 8 9
+========== 分页:每页 3 条 ==========
+第 1 页:0 1 2
+第 2 页:3 4 5
+第 3 页:6 7 8
+第 4 页:9
+```
+
+**思考题**:
+1. `limit` 和 `skip` 都是中间方法吗?它们各自返回什么?(提示:返回新的流对象,可继续链式调用)
+2. 分页的通用公式是什么?为什么第一页要 `skip(0)`?(提示:`skip((pageNo-1)*pageSize).limit(pageSize)`)
+3. 如果数据不够一页(如第 4 页只剩 1 条),`limit(3)` 会报错吗?(提示:不会,取到末尾就结束)
+
+---
+
+## 练习 4:concat 合并两流 + distinct 去重
+
+**难度**:⭐⭐
+**知识点**:`Stream.concat(stream a, stream b)`、`distinct()`、`Object.equals`、不可变集合 `List.of`
+
+**场景描述**:`concat` 是 Stream 接口的**静态方法**,把两个流合并成一个流;`distinct` 是去重中间方法,依据 `Object.equals(obj)` 判断元素是否相同。
+
+**题目要求**:
+
+```java
+import java.util.List;
+import java.util.stream.Stream;
+
+public class ConcatDistinctTest {
+    public static void main(String[] args) {
+        Stream<String> streamA = Stream.of("长贵", "赵四", "刘能");
+        Stream<String> streamB = Stream.of("旺财", "长贵", "赵四");
+
+        // 1. concat:合并两个流为一个流,并遍历输出
+        //    TODO: Stream.concat(streamA, streamB).forEach(System.out::println);
+
+        // 2. distinct:去重后再遍历输出(去掉重复的"长贵""赵四")
+        //    TODO: Stream.concat(streamA, streamB).distinct().forEach(System.out::println);
+
+        // 3. 给含重复元素的集合去重后遍历输出
+        List<String> list = List.of("长贵", "赵四", "郑伊健", "陈浩南", "刘能", "长贵", "赵四");
+        //    TODO: list.stream().distinct().forEach(System.out::println);
+    }
+}
+```
+
+**预期输出**:
+```
+========== concat 合并(有重复) ==========
+长贵 赵四 刘能 旺财 长贵 赵四
+========== concat + distinct 去重 ==========
+长贵 赵四 刘能 旺财
+========== List 去重 ==========
+长贵 赵四 郑伊健 陈浩南 刘能
+```
+
+**思考题**:
+1. `concat` 是流对象的方法还是 Stream 接口的静态方法?怎么调用?(提示:`Stream.concat(a, b)`,不是 `a.concat(b)`)
+2. `distinct` 判断重复的依据是什么?(提示:`Object.equals(obj)`——自定义对象需重写 equals/hashCode)
+3. `List.of(...)` 返回的集合能 `add` 吗?(提示:不可变集合,直接修改抛 UnsupportedOperationException)
+
+---
+
+## 练习 5:终结操作方法——count 计数 + forEach 遍历
+
+**难度**:⭐⭐
+**知识点**:终结方法概念、`long count()`、`forEach(Consumer)`、终结方法只能有一个
+
+**场景描述**:终结操作的意思是执行完这个方法后,**Stream 流就不能再执行其他操作**了。`forEach` 对每个元素执行动作,`count` 返回流中元素个数——它们都是流水线的**最后一步**。
+
+**题目要求**:
+
+```java
+import java.util.List;
+
+public class TerminateTest {
+    public static void main(String[] args) {
+        List<String> list = List.of("万震山", "万梓良", "万事如意", "王老五", "万巨龙", "张三");
+
+        // 1. forEach 遍历:输出每个元素
+        //    TODO: list.stream().forEach(System.out::println);
+
+        // 2. count 计数:统计以"万"开头的元素个数(filter + count)
+        //    TODO: long count = list.stream().filter(s -> s.startsWith("万")).count();
+        //    System.out.println(count);   // 期望 4
+
+        // 3. 挑战:在 forEach 之后再调用 .count(),观察会发生什么?
+        //    TODO: list.stream().forEach(System.out::println).count();  // 能编译吗?
+    }
+}
+```
+
+**预期输出**:
+```
+========== forEach 遍历 ==========
+万震山 万梓良 万事如意 王老五 万巨龙 张三
+========== count 计数 ==========
+以"万"开头的有:4 个
+========== 挑战结果 ==========
+forEach 之后再调 count() 会编译报错 —— 终结方法只能有一个,且调用后流已结束
+```
+
+**思考题**:
+1. `forEach` 和 `count` 属于三类方法中的哪一类?(提示:终结方法)
+2. `count()` 的返回值是什么类型?(提示:`long`)
+3. 为什么一条流水线只能有一个终结方法?(提示:终结方法执行后流就结束了)
+
+---
+
+## 练习 6:collect 收集操作——Collectors.toList() / toSet()
+
+**难度**:⭐⭐⭐
+**知识点**:`collect(Collector)`、`Collectors.toList()`、`Collectors.toSet()`、收集结果不可变、Set 天然去重
+
+**场景描述**:使用流对数据操作完毕后,可以**把流中的数据收集到集合**中——`collect` 是终结操作,配合工具类 `Collectors` 的 `toList()` / `toSet()` 把结果存起来,方便后续继续使用。
+
+**题目要求**:
+
+```java
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class CollectTest {
+    public static void main(String[] args) {
+        List<String> list = List.of("长贵", "赵四", "郑伊健", "陈浩南", "刘能",
+                "郑伊健", "陈浩南", "郑伊健", "陈浩南");
+
+        // 1. 去重后收集成 List 集合
+        //    TODO: List<String> list1 = list.stream().distinct().collect(Collectors.toList());
+        //    System.out.println(list1);
+
+        // 2. 过滤"伊"后收集成 Set 集合(Set 天然去重)
+        //    TODO: Set<String> set = list.stream().filter(s -> s.contains("伊"))
+        //                                   .collect(Collectors.toSet());
+        //    System.out.println(set);
+
+        // 3. 挑战:验证 toList() 返回的集合不可变——尝试 list1.add("旺财")
+        //    TODO: 观察抛出的异常类型(UnsupportedOperationException)
+    }
+}
+```
+
+**预期输出**:
+```
+========== distinct + toList ==========
+[长贵, 赵四, 郑伊健, 陈浩南, 刘能]
+========== filter + toSet ==========
+[郑伊健, 陈浩南]
+========== 挑战 ==========
+Exception in thread "main" java.lang.UnsupportedOperationException
+```
+
+**思考题**:
+1. `collect` 是中间操作还是终结操作?(提示:终结操作,执行完流结束,把结果收集到集合)
+2. `Collectors` 是什么?(提示:收集工具类,类比 Arrays / Collections)
+3. `toList()` 和 `toSet()` 返回的集合有什么特点?(提示:toList 不可变;toSet 天然去重且无序)
+
+---
+
+# 第二部分:进阶挑战
+
+> 以下练习综合运用 Stream 流 + 实体类 + Collectors 收集器。
+
+---
+
+## 进阶 1:Collectors.toMap 收集成 Map(学生信息映射)
+
+**难度**:⭐⭐⭐⭐
+**知识点**:`Collectors.toMap(keyMapper, valueMapper)`、`Function<T, R>` 函数式接口、Lambda vs 匿名内部类、`Map<Integer, Student>`
+
+**场景描述**:把流中的学生对象收集成 `Map<Integer, Student>`——键是**学号**(id),值是**学生对象**本身。`toMap` 的两个参数都是 `Function` 函数式接口:第一个从元素中取"键",第二个从元素中取"值"。
+
+**题目要求**:
+
+1. 定义 `Student` 实体类:
+
+```java
+import java.util.Objects;
+
+public class Student {
+    private Integer id;   // 学号
+    private String name;  // 姓名
+
+    // TODO: 无参构造 + 有参构造(id, name)
+    // TODO: getter / setter
+    // TODO: equals / hashCode —— 基于 id 和 name(Objects.equals / Objects.hash)
+    // TODO: toString
+}
+```
+
+2. 编写测试类 `StudentMapTest`:
+
+```java
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+public class StudentMapTest {
+    public static void main(String[] args) {
+        Student s1 = new Student(1001, "项羽");
+        Student s2 = new Student(1002, "张良");
+        Student s3 = new Student(1003, "项庄");
+        Student s4 = new Student(1004, "刘邦");
+        Student s5 = new Student(1005, "萧何");
+        Student s6 = new Student(1006, "韩信");
+        Student s7 = new Student(1007, "樊哙");
+        ArrayList<Student> list = new ArrayList<>(List.of(s1, s2, s3, s4, s5, s6, s7));
+
+        // 1. Lambda 写法:toMap(键, 值)
+        //    TODO: Map<Integer, Student> map = list.stream()
+        //              .collect(Collectors.toMap(
+        //                  student -> student.getId(),   // 键:学号
+        //                  student -> student            // 值:学生对象
+        //              ));
+        //    System.out.println(map);
+
+        // 2. 匿名内部类写法:Function 函数式接口(与 Lambda 等价)
+        //    TODO: Map<Integer, Student> map2 = list.stream()
+        //              .collect(Collectors.toMap(
+        //                  new Function<Student, Integer>() {
+        //                      public Integer apply(Student student) { return student.getId(); }
+        //                  },
+        //                  new Function<Student, Student>() {
+        //                      public Student apply(Student student) { return student; }
+        //                  }
+        //              ));
+
+        // 3. 挑战:用 get(1003) 取出"项庄",输出学生姓名
+        //    TODO: System.out.println(map.get(1003).getName());   // 期望 项庄
+    }
+}
+```
+
+**预期输出**:
+```
+{1001=Student{id=1001, name='项羽'}, 1002=Student{id=1002, name='张良'}, ...}
+========== 按学号取值 ==========
+项庄
+```
+
+**原理分析(必答)**:
+1. `Collectors.toMap` 的两个参数分别是什么?(提示:keyMapper 取键、valueMapper 取值,都是 `Function` 函数式接口)
+2. `student -> student.getId()` 与匿名内部类 `new Function<Student, Integer>(){...}` 是什么关系?(提示:等价,Lambda 是后者的简化)
+3. 如果两个学生学号相同(id 重复),`toMap` 会发生什么?(提示:默认抛 IllegalStateException——键必须唯一)
+
+---
+
+## 进阶 2:综合实战——员工信息流式处理
+
+**难度**:⭐⭐⭐⭐
+**知识点**:filter / distinct / count / collect / toMap 综合运用、`contains()` 判断、Stream 全流程
+
+**场景描述**:某公司有一批员工数据(工号、姓名、部门),用 Stream 流完成以下任务:过滤出技术部员工、按姓名去重、统计人数、收集成 Map(键=工号,值=姓名)。
+
+**题目要求**:
+
+1. 定义 `Employee` 类:
+
+```java
+public class Employee {
+    private String id;      // 工号
+    private String name;    // 姓名
+    private String dept;    // 部门
+
+    public Employee(String id, String name, String dept) {
+        this.id = id;
+        this.name = name;
+        this.dept = dept;
+    }
+
+    // TODO: getter / setter / toString
+}
+```
+
+2. 编写测试类 `EmployeeStreamTest`:
+
+```java
+import java.util.*;
+import java.util.stream.Collectors;
+
+public class EmployeeStreamTest {
+    public static void main(String[] args) {
+        List<Employee> list = new ArrayList<>(List.of(
+                new Employee("E001", "张三", "技术部"),
+                new Employee("E002", "李四", "市场部"),
+                new Employee("E003", "王五", "技术部"),
+                new Employee("E004", "张三", "技术部"),   // 姓名重复,测试去重
+                new Employee("E005", "赵六", "财务部")
+        ));
+
+        // 1. 过滤:只保留"技术部"的员工,遍历输出
+        //    TODO: list.stream().filter(e -> "技术部".equals(e.getDept())).forEach(...)
+
+        // 2. 去重:按姓名去重(先 map 取出姓名,再 distinct)
+        //    TODO: list.stream().map(Employee::getName).distinct().forEach(System.out::println);
+        //    期望:张三 王五 李四 赵六(注意:张三只出现一次)
+
+        // 3. 统计:技术部员工个数(filter + count)
+        //    TODO: long cnt = list.stream().filter(e -> "技术部".equals(e.getDept())).count();
+
+        // 4. 收集:收集成 Map<String, String>,键=工号,值=姓名
+        //    TODO: Map<String, String> map = list.stream()
+        //              .collect(Collectors.toMap(Employee::getId, Employee::getName));
+        //    System.out.println(map);
+    }
+}
+```
+
+**预期输出**:
+```
+========== 技术部员工 ==========
+E001-张三-技术部
+E003-王五-技术部
+E004-张三-技术部
+========== 按姓名去重 ==========
+张三 王五 李四 赵六
+========== 技术部人数统计 ==========
+3
+========== 收集成 Map(工号→姓名) ==========
+{E001=张三, E002=李四, E003=王五, E004=张三, E005=赵六}
+```
+
+**原理分析(必答)**:
+1. `Employee::getName` 是什么写法?等价于什么 Lambda?(提示:方法引用,等价于 `e -> e.getName()`)
+2. 任务 4 中 `toMap(Employee::getId, Employee::getName)` 的两个参数分别是什么?(提示:键=工号、值=姓名,都是方法引用)
+3. 任务 3 为什么能"先 filter 再 count"统计人数?(提示:filter 是中间方法返回新流,count 是终结方法统计个数)
+
+---
+
+# 参考答案要点
+
+## 练习 1 参考
+
+```java
+// 2. Map 体系集合
+Stream<String> keyStream = map.keySet().stream();    // key 就是 Set 集合
+Stream<Object> valueStream = map.values().stream();  // value 就是 List 集合
+
+// 3. 数组
+Stream<String> arrayStream = Arrays.stream(array);
+
+// 4. 相同类型的多个数据(Stream.of 本质是可变参数 T...)
+Stream<String> stringStream = Stream.of("长贵", "赵四", "刘能", "旺财");
+Stream<Integer> integerStream = Stream.of(10, 15, 20, 35, 46);
+```
+
+## 练习 2 参考
+
+```java
+// 1. 以"万"开头
+list.stream().filter(s -> s.startsWith("万")).forEach(System.out::println);
+
+// 2. 以"万"开头 且 长度为 3
+list.stream()
+        .filter(s -> s.startsWith("万"))
+        .filter(s -> s.length() == 3)
+        .forEach(System.out::println);
+
+// 3. 统计个数
+long count = list.stream()
+        .filter(s -> s.startsWith("万"))
+        .filter(s -> s.length() == 3)
+        .count();
+System.out.println("个数:" + count);   // 3
+```
+
+## 练习 3 参考
+
+```java
+// 1. limit(5):截取前 5 个
+Stream.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
+        .limit(5)
+        .forEach(n -> System.out.print(n + " "));
+
+// 2. skip(3):跳过前 3 个
+Stream.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
+        .skip(3)
+        .forEach(n -> System.out.print(n + " "));
+
+// 3. 分页方法:skip((pageNo-1)*pageSize).limit(pageSize)
+public void showPage(List<Integer> list, int pageNo, int pageSize) {
+    list.stream()
+        .skip((pageNo - 1) * pageSize)   // 跳过前面的页
+        .limit(pageSize)                 // 截取本页数量
+        .forEach(n -> System.out.print(n + " "));
+    System.out.println();
+}
+```
+
+## 练习 4 参考
+
+```java
+// 1. concat 合并(含重复)
+Stream.concat(streamA, streamB).forEach(System.out::println);
+// 长贵 赵四 刘能 旺财 长贵 赵四
+
+// 2. concat + distinct 去重
+Stream.concat(streamA, streamB).distinct().forEach(System.out::println);
+// 长贵 赵四 刘能 旺财
+
+// 3. List 去重
+list.stream().distinct().forEach(System.out::println);
+// 长贵 赵四 郑伊健 陈浩南 刘能
+```
+
+## 练习 5 参考
+
+```java
+// 1. forEach 遍历
+list.stream().forEach(System.out::println);
+
+// 2. count 计数
+long count = list.stream().filter(s -> s.startsWith("万")).count();
+System.out.println("以\"万\"开头的有:" + count + " 个");
+
+// 3. 挑战:forEach 后再调 count() → 编译报错
+// 原因:forEach 是终结方法,调用后流已结束,不能再继续链式操作
+// list.stream().forEach(System.out::println).count();  // ✗ 编译错误
+```
+
+## 练习 6 参考
+
+```java
+// 1. 去重后收集成 List
+List<String> list1 = list.stream().distinct().collect(Collectors.toList());
+System.out.println(list1);
+
+// 2. 过滤"伊"后收集成 Set
+Set<String> set = list.stream()
+        .filter(s -> s.contains("伊"))
+        .collect(Collectors.toSet());
+System.out.println(set);
+
+// 3. 挑战:toList() 返回不可变集合,add 抛 UnsupportedOperationException
+// list1.add("旺财");  // ✗ 运行时抛 UnsupportedOperationException
+```
+
+## 进阶 1 参考(核心逻辑)
+
+```java
+// Student:equals/hashCode 基于 id + name
+@Override
+public boolean equals(Object o) {
+    if (o == null || getClass() != o.getClass()) return false;
+    Student student = (Student) o;
+    return Objects.equals(id, student.id) && Objects.equals(name, student.name);
+}
+
+@Override
+public int hashCode() {
+    return Objects.hash(id, name);
+}
+
+@Override
+public String toString() {
+    return "Student{id=" + id + ", name='" + name + "'}";
+}
+
+// StudentMapTest:Lambda 方式收集成 Map
+Map<Integer, Student> map = list.stream()
+        .collect(Collectors.toMap(
+                student -> student.getId(),   // 键:学号
+                student -> student            // 值:学生对象
+        ));
+System.out.println(map);
+System.out.println(map.get(1003).getName());   // 项庄
+```
+
+## 进阶 2 参考(核心逻辑)
+
+```java
+// 1. 过滤技术部
+list.stream()
+    .filter(e -> "技术部".equals(e.getDept()))
+    .forEach(System.out::println);
+
+// 2. 按姓名去重(map 取出姓名 → distinct 去重)
+list.stream()
+    .map(Employee::getName)
+    .distinct()
+    .forEach(System.out::println);
+
+// 3. 统计技术部人数
+long cnt = list.stream()
+    .filter(e -> "技术部".equals(e.getDept()))
+    .count();
+
+// 4. 收集成 Map(键=工号,值=姓名)
+Map<String, String> map = list.stream()
+    .collect(Collectors.toMap(Employee::getId, Employee::getName));
+System.out.println(map);
+```
+
+---
+
+# 涵盖知识点总览
+
+| 知识点 | 对应练习 | 说明 |
+|--------|---------|------|
+| 生成流的四种方式 | 练习 1 | Collection.stream() / Map keySet/values / Arrays.stream / Stream.of |
+| Stream.of 与可变参数 | 练习 1 | `of(T...values)` 本质是可变参数,可传任意个数数据 |
+| filter 过滤(Predicate) | 练习 2、3、5、6、进阶 2 | 传入断言条件,返回符合条件的新流 |
+| forEach 遍历(Consumer) | 练习 2、5 | 对每个元素执行动作,方法引用 System.out::println |
+| 中间方法链式调用 | 练习 3 | 中间方法返回新流对象,可一直链式 |
+| limit 截取 / skip 跳过 | 练习 3 | 分页公式 skip((n-1)*size).limit(size) |
+| concat 合并两流 | 练习 4 | Stream 接口静态方法 Stream.concat(a, b) |
+| distinct 去重 | 练习 4、6 | 依据 Object.equals(obj) |
+| 终结方法只能有一个 | 练习 5 | forEach/count 调用后流结束 |
+| count 计数(long) | 练习 5、进阶 2 | 返回元素个数,类型 long |
+| collect 收集操作 | 练习 6、进阶 1、进阶 2 | 把流结果收集到集合,配合 Collectors |
+| Collectors.toList() / toSet() | 练习 6 | toList 不可变;toSet 天然去重 |
+| Collectors.toMap(keyMapper, valueMapper) | 进阶 1、进阶 2 | 两个 Function 参数:取键 / 取值 |
+| Function 函数式接口 | 进阶 1 | 有参有返回值;Lambda vs 匿名内部类 |
+| 方法引用 类::方法 | 进阶 2 | Employee::getName ≈ e -> e.getName() |
+| Stream + 实体类综合 | 进阶 1、进阶 2 | 实体类 + equals/hashCode + 收集成 Map |
+