Java Stream 流式编程 10天系统学习计划

前置要求:掌握Java基础语法、集合框架、Lambda表达式、Optional基础用法;JDK版本建议1.8及以上,示例兼容JDK8,标注JDK16+新增特性 核心设计原则:循序渐进,从入门到进阶,每天知识点闭环,学练结合,覆盖90%日常开发高频场景


通用前置说明

所有示例统一使用的基础实体类,后续每日代码均复用该类,不再重复定义:

kotlin 复制代码
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;

// 用户实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Long id;        // 用户ID
    private String name;    // 用户名
    private int age;        // 年龄
    private String gender;  // 性别
    private Long deptId;    // 部门ID
    private String deptName;// 部门名称
    private double salary;  // 薪资
    private String phone;   // 手机号
}

// 订单实体类(Day10实战使用)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order {
    private Long orderId;
    private Long userId;
    private String goodsName;
    private double amount;
    private LocalDateTime orderTime;
}

Day1:Stream核心概念与流的创建

1. 知识点

  1. Stream核心定义 :JDK8引入的集合数据处理抽象层,不是数据结构,不存储数据,是一套数据处理的流水线API,基于Lambda表达式实现声明式编程。
  2. Stream与集合的核心区别
特性 Stream流 集合
核心用途 数据计算、转换、聚合 数据存储
迭代方式 内部迭代(JVM控制,自动优化) 外部迭代(手动for/iterator遍历)
求值特性 惰性求值(终止操作才触发计算) 即时存储
消费特性 一次性消费(消费后无法重复使用) 可重复遍历
数据修改 不修改源数据,只生成新数据 可修改源数据
  1. Stream操作的两大阶段

    1. 中间操作:返回Stream本身,可链式调用,不触发计算,仅记录操作
    2. 终止操作:返回非Stream结果,触发流水线计算,流消费后失效
  2. Stream的6种标准创建方式

    1. 集合创建:Collection.stream()(串行流)/parallelStream()(并行流)
    2. 数组创建:Arrays.stream(T[] array)
    3. 静态方法创建:Stream.of(T... values)
    4. 无限流-迭代:Stream.iterate(T seed, UnaryOperator<T> f)
    5. 无限流-生成:Stream.generate(Supplier<T> s)
    6. IO流创建:Files.lines(Path path)

2. 示例代码

arduino 复制代码
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
import java.nio.file.Files;
import java.nio.file.Paths;

public class Day1StreamCreateDemo {
    public static void main(String[] args) {
        // 1. 集合创建流(最常用)
        List<String> nameList = Arrays.asList("张三", "李四", "王五");
        Stream<String> listStream = nameList.stream();
        listStream.forEach(System.out::println);

        // 2. 数组创建流
        Integer[] numArray = {1, 2, 3, 4, 5};
        Stream<Integer> arrayStream = Arrays.stream(numArray);
        arrayStream.forEach(System.out::println);

        // 3. Stream.of静态方法创建
        Stream<String> ofStream = Stream.of("Java", "Stream", "Lambda");
        ofStream.forEach(System.out::println);

        // 4. iterate创建无限流(需配合limit截断)
        Stream<Integer> iterateStream = Stream.iterate(0, n -> n + 2).limit(5);
        iterateStream.forEach(System.out::println); // 输出0,2,4,6,8

        // 5. generate创建无限流
        Stream<Double> generateStream = Stream.generate(Math::random).limit(3);
        generateStream.forEach(System.out::println); // 输出3个随机数

        // 6. 文件流创建
        try (Stream<String> fileStream = Files.lines(Paths.get("test.txt"))) {
            System.out.println("文件行数:" + fileStream.count());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3. 练习题

  1. 用3种不同的方式,创建包含1-10整数的串行Stream流,并输出所有元素。
  2. 使用Stream.iterate()创建无限流,生成并输出前10个正偶数。
  3. 读取本地demo.txt文件,按行创建Stream,统计并输出文件中非空行的数量。

4. 练习题答案

java 复制代码
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
import java.nio.file.Files;
import java.nio.file.Paths;

public class Day1ExerciseAnswer {
    public static void main(String[] args) {
        // 第1题:3种方式创建1-10的流
        System.out.println("=====第1题答案=====");
        // 方式1:集合创建
        List<Integer> numList = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
        numList.stream().forEach(n -> System.out.print(n + " "));
        System.out.println();

        // 方式2:数组创建
        Integer[] numArray = {1,2,3,4,5,6,7,8,9,10};
        Arrays.stream(numArray).forEach(n -> System.out.print(n + " "));
        System.out.println();

        // 方式3:Stream.of创建
        Stream.of(1,2,3,4,5,6,7,8,9,10).forEach(n -> System.out.print(n + " "));
        System.out.println();

        // 第2题:iterate生成前10个正偶数
        System.out.println("=====第2题答案=====");
        Stream.iterate(2, n -> n + 2).limit(10).forEach(n -> System.out.print(n + " "));
        System.out.println();

        // 第3题:统计文件非空行数量
        System.out.println("=====第3题答案=====");
        try (Stream<String> lines = Files.lines(Paths.get("demo.txt"))) {
            long notEmptyCount = lines.filter(line -> !line.trim().isEmpty()).count();
            System.out.println("非空行数量:" + notEmptyCount);
        } catch (Exception e) {
            System.out.println("文件读取失败:" + e.getMessage());
        }
    }
}

Day2:Stream中间操作-筛选与切片

1. 知识点

  1. 中间操作核心特性 :返回Stream对象,支持链式调用,仅记录操作,不触发计算,只有调用终止操作时才会执行。
  2. 四大核心筛选&切片API
API 作用 入参类型 注意事项
filter(Predicate<? super T> predicate) 过滤元素,保留断言为true的元素 Predicate断言型函数式接口 提前过滤可减少后续操作的数据量,提升性能
distinct() 元素去重,基于equals()hashCode()判断 无参 自定义对象必须重写equals和hashCode方法,否则去重失效
limit(long maxSize) 截断流,只保留前maxSize个元素 long类型数值 短路操作,匹配到足够元素后会提前终止流处理
skip(long n) 跳过前n个元素,返回剩余元素组成的流 long类型数值 配合limit可实现分页功能,大数据量下深分页性能较差
  1. 操作组合规则:筛选类操作建议放在流水线最前端,提前缩小数据范围,提升整体执行效率。

2. 示例代码

csharp 复制代码
import java.util.Arrays;
import java.util.List;

public class Day2FilterDemo {
    public static void main(String[] args) {
        List<Integer> numList = Arrays.asList(1,2,2,3,4,4,5,6,7,8,9,10);
        List<User> userList = Arrays.asList(
                new User(1L, "张三", 22, "男", 1L, "研发部", 8000, "13800138000"),
                new User(2L, "李四", 28, "女", 1L, "研发部", 12000, "13800138001"),
                new User(3L, "王五", 35, "男", 2L, "产品部", 15000, "13800138002"),
                new User(4L, "赵六", 28, "男", 2L, "产品部", 10000, "13800138003"),
                new User(2L, "李四", 28, "女", 1L, "研发部", 12000, "13800138001") // 重复数据
        );

        // 1. filter过滤:保留大于5的偶数
        System.out.println("=====filter过滤=====");
        numList.stream()
                .filter(n -> n > 5 && n % 2 == 0)
                .forEach(System.out::println);

        // 2. distinct去重
        System.out.println("=====distinct去重=====");
        numList.stream().distinct().forEach(System.out::println);
        // 自定义对象去重(需重写equals和hashCode)
        userList.stream().distinct().forEach(user -> System.out.println(user.getName()));

        // 3. limit截断:取前3个元素
        System.out.println("=====limit截断=====");
        numList.stream().limit(3).forEach(System.out::println);

        // 4. skip跳过:跳过前3个元素
        System.out.println("=====skip跳过=====");
        numList.stream().skip(3).forEach(System.out::println);

        // 5. 组合操作:分页查询(第2页,每页2条)+ 过滤研发部用户
        System.out.println("=====组合操作:分页+过滤=====");
        int pageNum = 2;
        int pageSize = 2;
        userList.stream()
                .filter(user -> "研发部".equals(user.getDeptName()))
                .skip((pageNum - 1) * pageSize)
                .limit(pageSize)
                .forEach(user -> System.out.println(user.getName()));
    }
}

3. 练习题

  1. List<Integer> nums = Arrays.asList(1,3,5,7,9,2,4,6,8,10,11,13,15),过滤出大于3且小于12的奇数,输出结果。
  2. 对User列表,按用户ID去重,过滤出年龄大于25岁的女性用户,输出用户名和年龄。
  3. 对1-100的整数流,跳过前30个元素,取15个元素,过滤出其中能被3整除的数,统计符合条件的元素个数。

4. 练习题答案

csharp 复制代码
import java.util.Arrays;
import java.util.List;

public class Day2ExerciseAnswer {
    public static void main(String[] args) {
        // 第1题
        System.out.println("=====第1题答案=====");
        List<Integer> nums = Arrays.asList(1,3,5,7,9,2,4,6,8,10,11,13,15);
        nums.stream()
                .filter(n -> n > 3 && n < 12 && n % 2 != 0)
                .forEach(System.out::println);

        // 第2题
        System.out.println("=====第2题答案=====");
        List<User> userList = Arrays.asList(
                new User(1L, "张三", 22, "男", 1L, "研发部", 8000, "13800138000"),
                new User(2L, "李四", 28, "女", 1L, "研发部", 12000, "13800138001"),
                new User(3L, "王五", 35, "男", 2L, "产品部", 15000, "13800138002"),
                new User(4L, "孙七", 30, "女", 3L, "人事部", 9000, "13800138004"),
                new User(2L, "李四", 28, "女", 1L, "研发部", 12000, "13800138001")
        );
        userList.stream()
                .distinct()
                .filter(user -> user.getAge() > 25 && "女".equals(user.getGender()))
                .forEach(user -> System.out.println("姓名:" + user.getName() + ",年龄:" + user.getAge()));

        // 第3题
        System.out.println("=====第3题答案=====");
        long count = Stream.iterate(1, n -> n + 1)
                .limit(100)
                .skip(30)
                .limit(15)
                .filter(n -> n % 3 == 0)
                .count();
        System.out.println("符合条件的元素个数:" + count);
    }
}

Day3:Stream中间操作-映射(map与flatMap)

1. 知识点

  1. 映射操作核心作用:将流中的每个元素按照指定规则进行转换,生成新的元素,是Stream中最常用的元素转换操作。

  2. map映射(一对一转换)

    1. 核心API:<R> Stream<R> map(Function<? super T, ? extends R> mapper)

    2. 入参:Function函数式接口,接收一个T类型元素,返回一个R类型元素

    3. 核心特性:每个输入元素,都会映射生成一个输出元素,实现一对一的元素转换

    4. 原始类型优化API(避免自动装箱拆箱,提升性能):

      • mapToInt(ToIntFunction<? super T> mapper):返回IntStream
      • mapToLong(ToLongFunction<? super T> mapper):返回LongStream
      • mapToDouble(ToDoubleFunction<? super T> mapper):返回DoubleStream
  3. flatMap扁平化映射(一对多转换)

    1. 核心API:<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
    2. 入参:Function函数式接口,接收一个T类型元素,返回一个Stream流
    3. 核心特性:每个输入元素,映射生成一个流 ,最终将所有流扁平化合并为一个流,解决「流中流」的嵌套问题
  4. map与flatMap核心区别:map是「元素转元素」,flatMap是「元素转流,再把所有流合并」

2. 示例代码

arduino 复制代码
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class Day3MapDemo {
    public static void main(String[] args) {
        List<String> wordList = Arrays.asList("Java", "Stream", "Lambda");
        List<User> userList = Arrays.asList(
                new User(1L, "张三", 22, "男", 1L, "研发部", 8000, "13800138000"),
                new User(2L, "李四", 28, "女", 1L, "研发部", 12000, "13800138001"),
                new User(3L, "王五", 35, "男", 2L, "产品部", 15000, "13800138002")
        );

        // 1. map基础用法:字符串转大写
        System.out.println("=====map转大写=====");
        wordList.stream()
                .map(String::toUpperCase)
                .forEach(System.out::println);

        // 2. map提取对象属性:提取所有用户名
        System.out.println("=====map提取对象属性=====");
        userList.stream()
                .map(User::getName)
                .forEach(System.out::println);

        // 3. mapToInt原始类型流:计算薪资总和
        System.out.println("=====mapToInt计算总和=====");
        double totalSalary = userList.stream()
                .mapToDouble(User::getSalary)
                .sum();
        System.out.println("薪资总和:" + totalSalary);

        // 4. map与flatMap对比:拆分单词为字母
        System.out.println("=====map拆分单词(流中流)=====");
        // map返回的是Stream<String[]>,每个元素是数组,嵌套流
        Stream<String[]> mapStream = wordList.stream().map(word -> word.split(""));
        mapStream.forEach(array -> System.out.println(Arrays.toString(array)));

        System.out.println("=====flatMap拆分单词(扁平化)=====");
        // flatMap把每个数组转成流,再合并为一个流
        wordList.stream()
                .map(word -> word.split(""))
                .flatMap(Arrays::stream)
                .forEach(System.out::println);

        // 5. flatMap扁平化嵌套集合
        System.out.println("=====flatMap扁平化嵌套集合=====");
        List<List<Integer>> nestedList = Arrays.asList(
                Arrays.asList(1,2,3),
                Arrays.asList(4,5,6),
                Arrays.asList(7,8,9)
        );
        nestedList.stream()
                .flatMap(List::stream)
                .forEach(System.out::println);
    }
}

3. 练习题

  1. List<String> strList = Arrays.asList("apple", "banana", "orange", "grape"),将每个字符串转为其长度的整数流,输出每个字符串的长度。
  2. 对User列表,提取所有用户的手机号,转为Long类型的流,去重后输出。
  3. 给定List<List<Integer>> numNestedList = Arrays.asList(Arrays.asList(1,2,2,3), Arrays.asList(3,4,5,5), Arrays.asList(6,7,8,9)),将嵌套集合扁平化为一个整数流,去重后求和,输出最终结果。

4. 练习题答案

go 复制代码
import java.util.Arrays;
import java.util.List;

public class Day3ExerciseAnswer {
    public static void main(String[] args) {
        // 第1题
        System.out.println("=====第1题答案=====");
        List<String> strList = Arrays.asList("apple", "banana", "orange", "grape");
        strList.stream()
                .map(String::length)
                .forEach(len -> System.out.print(len + " "));
        System.out.println();

        // 第2题
        System.out.println("=====第2题答案=====");
        List<User> userList = Arrays.asList(
                new User(1L, "张三", 22, "男", 1L, "研发部", 8000, "13800138000"),
                new User(2L, "李四", 28, "女", 1L, "研发部", 12000, "13800138001"),
                new User(3L, "王五", 35, "男", 2L, "产品部", 15000, "13800138002"),
                new User(4L, "赵六", 28, "男", 2L, "产品部", 10000, "13800138001")
        );
        userList.stream()
                .map(User::getPhone)
                .map(Long::valueOf)
                .distinct()
                .forEach(System.out::println);

        // 第3题
        System.out.println("=====第3题答案=====");
        List<List<Integer>> numNestedList = Arrays.asList(
                Arrays.asList(1,2,2,3),
                Arrays.asList(3,4,5,5),
                Arrays.asList(6,7,8,9)
        );
        int sum = numNestedList.stream()
                .flatMap(List::stream)
                .distinct()
                .mapToInt(Integer::intValue)
                .sum();
        System.out.println("去重后求和结果:" + sum);
    }
}

Day4:Stream中间操作-排序与peek调试

1. 知识点

  1. sorted排序操作

    1. 核心作用:对流中的元素进行排序,属于有状态的中间操作(需要获取所有元素后才能完成排序)

    2. 两个重载方法:

      • Stream<T> sorted():无参,自然排序,要求流中元素必须实现Comparable接口,否则抛出ClassCastException
      • Stream<T> sorted(Comparator<? super T> comparator):有参,定制排序,传入Comparator比较器,自定义排序规则
    3. 高级排序技巧:

      • 升序/降序:Comparator.naturalOrder()自然升序,Comparator.reverseOrder()反转降序
      • 多条件排序:使用thenComparing()实现多字段组合排序,先按第一个条件排序,相同则按第二个条件排序
      • 空值处理:Comparator.nullsFirst()/nullsLast()处理排序字段为null的元素,避免空指针
  2. peek调试操作

    1. 核心API:Stream<T> peek(Consumer<? super T> action)
    2. 核心作用:对流中的每个元素执行Consumer操作,不改变元素本身,主要用于流处理的调试,查看流水线中每个步骤的元素状态
    3. 与forEach的核心区别:peek是中间操作 ,不会触发流的计算;forEach是终止操作,会触发流的计算
    4. 注意事项:peek仅用于调试,禁止在生产环境中用peek执行业务逻辑,并行流中peek的执行顺序不可控

2. 示例代码

less 复制代码
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

public class Day4SortedPeekDemo {
    public static void main(String[] args) {
        List<Integer> numList = Arrays.asList(5, 2, 9, 1, 7, 3, 8, 4, 6);
        List<User> userList = Arrays.asList(
                new User(1L, "张三", 22, "男", 1L, "研发部", 8000, "13800138000"),
                new User(2L, "李四", 28, "女", 1L, "研发部", 12000, "13800138001"),
                new User(3L, "王五", 35, "男", 2L, "产品部", 15000, "13800138002"),
                new User(4L, "赵六", 28, "男", 2L, "产品部", 12000, "13800138003"),
                new User(5L, "孙七", 30, "女", 3L, "人事部", 9000, "13800138004")
        );

        // 1. 自然排序
        System.out.println("=====自然排序=====");
        numList.stream().sorted().forEach(System.out::println);

        // 2. 定制排序:降序
        System.out.println("=====降序排序=====");
        numList.stream().sorted(Comparator.reverseOrder()).forEach(System.out::println);

        // 3. 单字段定制排序:按用户年龄升序
        System.out.println("=====按年龄升序=====");
        userList.stream()
                .sorted(Comparator.comparingInt(User::getAge))
                .forEach(user -> System.out.println(user.getName() + ":" + user.getAge()));

        // 4. 多条件排序:先按年龄升序,年龄相同按薪资降序
        System.out.println("=====多条件排序=====");
        userList.stream()
                .sorted(Comparator.comparingInt(User::getAge)
                        .thenComparingDouble(User::getSalary).reversed())
                .forEach(user -> System.out.println(user.getName() + ":年龄" + user.getAge() + ",薪资" + user.getSalary()));

        // 5. peek调试:查看每个步骤的元素变化,验证惰性求值
        System.out.println("=====peek调试=====");
        List<String> result = numList.stream()
                .filter(n -> n % 2 == 0)
                .peek(n -> System.out.println("过滤后:" + n))
                .map(n -> "数字:" + n)
                .peek(s -> System.out.println("映射后:" + s))
                .limit(3)
                .toList();
        System.out.println("最终结果:" + result);
    }
}

3. 练习题

  1. List<String> strList = Arrays.asList("banana", "apple", "grape", "orange", "pear"),按字符串长度升序排序,长度相同则按字典序降序排序,输出结果。
  2. 对User列表,先按部门ID升序排序,部门ID相同则按薪资降序排序,薪资相同则按年龄升序排序,输出用户名、部门ID、薪资、年龄。
  3. 构建一个包含filter、map、sorted、limit的流处理流水线,使用peek在每个步骤后输出元素,验证Stream的惰性求值特性和执行顺序。

4. 练习题答案

less 复制代码
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

public class Day4ExerciseAnswer {
    public static void main(String[] args) {
        // 第1题
        System.out.println("=====第1题答案=====");
        List<String> strList = Arrays.asList("banana", "apple", "grape", "orange", "pear");
        strList.stream()
                .sorted(Comparator.comparingInt(String::length)
                        .thenComparing(Comparator.reverseOrder()))
                .forEach(System.out::println);

        // 第2题
        System.out.println("=====第2题答案=====");
        List<User> userList = Arrays.asList(
                new User(1L, "张三", 22, "男", 1L, "研发部", 8000, "13800138000"),
                new User(2L, "李四", 28, "女", 1L, "研发部", 12000, "13800138001"),
                new User(3L, "王五", 35, "男", 2L, "产品部", 15000, "13800138002"),
                new User(4L, "赵六", 28, "男", 2L, "产品部", 12000, "13800138003"),
                new User(5L, "孙七", 30, "女", 3L, "人事部", 9000, "13800138004"),
                new User(6L, "周八", 26, "男", 2L, "产品部", 12000, "13800138005")
        );
        userList.stream()
                .sorted(Comparator.comparingLong(User::getDeptId)
                        .thenComparingDouble(User::getSalary).reversed()
                        .thenComparingInt(User::getAge))
                .forEach(user -> System.out.printf("姓名:%s,部门ID:%d,薪资:%.0f,年龄:%d%n",
                        user.getName(), user.getDeptId(), user.getSalary(), user.getAge()));

        // 第3题:peek验证惰性求值
        System.out.println("=====第3题答案=====");
        List<Integer> numList = Arrays.asList(5, 2, 9, 1, 7, 3, 8, 4, 6);
        System.out.println("开始构建流,此时中间操作不会执行");
        List<String> result = numList.stream()
                .filter(n -> {
                    System.out.println("执行filter:" + n);
                    return n > 3;
                })
                .peek(n -> System.out.println("peek1-filter后:" + n))
                .map(n -> n * 2)
                .peek(n -> System.out.println("peek2-map后:" + n))
                .sorted()
                .peek(n -> System.out.println("peek3-sorted后:" + n))
                .limit(3)
                .map(n -> "结果:" + n)
                .toList();
        System.out.println("终止操作执行完毕,最终结果:" + result);
    }
}

Day5:Stream终止操作-匹配与查找

1. 知识点

  1. 终止操作核心特性:触发Stream流水线的完整计算,返回非Stream类型的结果,流一旦执行终止操作就会被消费,无法重复使用。

  2. 匹配操作(返回boolean,短路操作)

    1. 短路特性:匹配到符合条件的元素后,会立即终止流的处理,无需遍历所有元素,提升性能
    API 作用 入参 短路触发条件
    boolean allMatch(Predicate<? super T> predicate) 判断所有元素是否都匹配断言 Predicate断言 遇到第一个不匹配的元素,立即返回false
    boolean anyMatch(Predicate<? super T> predicate) 判断是否存在至少一个元素匹配断言 Predicate断言 遇到第一个匹配的元素,立即返回true
    boolean noneMatch(Predicate<? super T> predicate) 判断所有元素都不匹配断言 Predicate断言 遇到第一个匹配的元素,立即返回false
  3. 查找操作(返回Optional,避免空指针)

API 作用 适用场景
Optional<T> findFirst() 返回流中的第一个元素 串行流,需要明确获取第一个元素
Optional<T> findAny() 返回流中的任意一个元素 并行流,无需固定元素,只需获取任意一个符合条件的元素,性能更高
  1. 简单统计操作
API 作用 返回值
long count() 统计流中元素的个数 long
Optional<T> max(Comparator<? super T> comparator) 按比较器返回流中的最大值 Optional
Optional<T> min(Comparator<? super T> comparator) 按比较器返回流中的最小值 Optional
  1. Optional核心用法:所有查找操作都返回Optional,用于规避空指针异常,常用方法:

    1. ifPresent(Consumer<? super T> action):值存在时执行操作
    2. orElse(T other):值不存在时返回默认值
    3. orElseThrow(Supplier<? extends X> exceptionSupplier):值不存在时抛出异常

2. 示例代码

java 复制代码
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;

public class Day5MatchFindDemo {
    public static void main(String[] args) {
        List<Integer> numList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        List<User> userList = Arrays.asList(
                new User(1L, "张三", 22, "男", 1L, "研发部", 8000, "13800138000"),
                new User(2L, "李四", 28, "女", 1L, "研发部", 12000, "13800138001"),
                new User(3L, "王五", 35, "男", 2L, "产品部", 15000, "13800138002"),
                new User(4L, "赵六", 28, "男", 2L, "产品部", 12000, "13800138003"),
                new User(5L, "孙七", 30, "女", 3L, "人事部", 9000, "13800138004")
        );

        // 1. 匹配操作
        System.out.println("=====匹配操作=====");
        // allMatch:所有元素都大于0
        boolean allPositive = numList.stream().allMatch(n -> n > 0);
        System.out.println("所有元素都大于0:" + allPositive);

        // anyMatch:是否存在偶数
        boolean hasEven = numList.stream().anyMatch(n -> n % 2 == 0);
        System.out.println("是否存在偶数:" + hasEven);

        // noneMatch:没有元素大于10
        boolean noneGt10 = numList.stream().noneMatch(n -> n > 10);
        System.out.println("没有元素大于10:" + noneGt10);

        // 2. 查找操作
        System.out.println("=====查找操作=====");
        // findFirst:查找第一个大于5的元素
        Optional<Integer> firstGt5 = numList.stream().filter(n -> n > 5).findFirst();
        firstGt5.ifPresent(n -> System.out.println("第一个大于5的元素:" + n));

        // findAny:并行流中查找任意一个年龄大于25的用户
        Optional<User> anyUser = userList.parallelStream().filter(user -> user.getAge() > 25).findAny();
        anyUser.ifPresent(user -> System.out.println("任意一个年龄大于25的用户:" + user.getName()));

        // 3. 统计操作
        System.out.println("=====统计操作=====");
        // count:统计研发部人数
        long deptCount = userList.stream().filter(user -> "研发部".equals(user.getDeptName())).count();
        System.out.println("研发部人数:" + deptCount);

        // max:查找薪资最高的用户
        Optional<User> maxSalaryUser = userList.stream().max(Comparator.comparingDouble(User::getSalary));
        maxSalaryUser.ifPresent(user -> System.out.println("薪资最高的用户:" + user.getName() + ",薪资:" + user.getSalary()));

        // min:查找年龄最小的用户
        Optional<User> minAgeUser = userList.stream().min(Comparator.comparingInt(User::getAge));
        minAgeUser.ifPresent(user -> System.out.println("年龄最小的用户:" + user.getName() + ",年龄:" + user.getAge()));
    }
}

3. 练习题

  1. 判断List<Integer> nums = Arrays.asList(2,4,6,8,10,12,14)中是否所有元素都是偶数,输出结果。
  2. 对User列表,查找薪资大于10000的年龄最小的用户,输出用户名、年龄、薪资。
  3. 判断User列表中是否没有薪资低于8000的用户,输出结果。
  4. 使用并行流,从User列表中查找任意一个研发部的女性用户,若存在则输出用户名,不存在则输出"无符合条件的用户"。

4. 练习题答案

java 复制代码
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;

public class Day5ExerciseAnswer {
    public static void main(String[] args) {
        // 第1题
        System.out.println("=====第1题答案=====");
        List<Integer> nums = Arrays.asList(2,4,6,8,10,12,14);
        boolean allEven = nums.stream().allMatch(n -> n % 2 == 0);
        System.out.println("所有元素都是偶数:" + allEven);

        // 第2题
        System.out.println("=====第2题答案=====");
        List<User> userList = Arrays.asList(
                new User(1L, "张三", 22, "男", 1L, "研发部", 8000, "13800138000"),
                new User(2L, "李四", 28, "女", 1L, "研发部", 12000, "13800138001"),
                new User(3L, "王五", 35, "男", 2L, "产品部", 15000, "13800138002"),
                new User(4L, "赵六", 26, "男", 2L, "产品部", 11000, "13800138003"),
                new User(5L, "孙七", 30, "女", 3L, "人事部", 10500, "13800138004")
        );
        Optional<User> targetUser = userList.stream()
                .filter(user -> user.getSalary() > 10000)
                .min(Comparator.comparingInt(User::getAge));
        targetUser.ifPresentOrElse(
                user -> System.out.printf("符合条件的用户:%s,年龄:%d,薪资:%.0f%n",
                        user.getName(), user.getAge(), user.getSalary()),
                () -> System.out.println("无符合条件的用户")
        );

        // 第3题
        System.out.println("=====第3题答案=====");
        boolean noneLowSalary = userList.stream().noneMatch(user -> user.getSalary() < 8000);
        System.out.println("没有薪资低于8000的用户:" + noneLowSalary);

        // 第4题
        System.out.println("=====第4题答案=====");
        Optional<User> anyFemaleUser = userList.parallelStream()
                .filter(user -> "研发部".equals(user.getDeptName()) && "女".equals(user.getGender()))
                .findAny();
        System.out.println(anyFemaleUser.map(User::getName).orElse("无符合条件的用户"));
    }
}

Day6:Stream终止操作-规约reduce

1. 知识点

  1. reduce核心作用 :将流中的元素按照指定的累加规则,依次进行计算,最终规约为一个值,是Stream所有聚合操作的底层实现(sum、max、min等都是基于reduce实现的)。
  2. reduce的三个重载方法
重载方法 核心定义 返回值 适用场景
Optional<T> reduce(BinaryOperator<T> accumulator) 无初始值,仅传入累加器 Optional 流可能为空,无默认初始值,避免空指针
T reduce(T identity, BinaryOperator<T> accumulator) 有初始值+累加器 T(与初始值同类型) 流为空时返回初始值,有明确的默认值场景
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner) 初始值+累加器+组合器 U(可与流元素类型不同) 并行流规约,类型转换场景,并行流中必须保证组合器与累加器结果一致
  1. 核心参数说明

    1. identity:初始值,也是流为空时的默认返回值,必须是累加器的恒等值(比如加法的0,乘法的1)
    2. accumulator:累加器,定义两个元素的计算规则,第一个参数是上一次的计算结果,第二个参数是当前流中的元素
    3. combiner:组合器,仅在并行流中生效,用于合并多个线程的计算结果
  2. map-reduce模式:先通过map将元素转换为需要计算的类型,再通过reduce进行聚合,是大数据处理的经典模式,也是Stream最常用的开发范式。

2. 示例代码

java 复制代码
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class Day6ReduceDemo {
    public static void main(String[] args) {
        List<Integer> numList = Arrays.asList(1, 2, 3, 4, 5);
        List<User> userList = Arrays.asList(
                new User(1L, "张三", 22, "男", 1L, "研发部", 8000, "13800138000"),
                new User(2L, "李四", 28, "女", 1L, "研发部", 12000, "13800138001"),
                new User(3L, "王五", 35, "男", 2L, "产品部", 15000, "13800138002"),
                new User(4L, "赵六", 28, "男", 2L, "产品部", 12000, "13800138003")
        );

        // 1. 一个参数的reduce:无初始值,求和
        System.out.println("=====一个参数reduce求和=====");
        Optional<Integer> sumOptional = numList.stream().reduce((a, b) -> a + b);
        sumOptional.ifPresent(sum -> System.out.println("求和结果:" + sum));

        // 2. 一个参数的reduce:求最大值
        System.out.println("=====一个参数reduce求最大值=====");
        Optional<Integer> maxOptional = numList.stream().reduce(Integer::max);
        maxOptional.ifPresent(max -> System.out.println("最大值:" + max));

        // 3. 两个参数的reduce:有初始值,求和
        System.out.println("=====两个参数reduce求和=====");
        Integer sum = numList.stream().reduce(0, Integer::sum);
        System.out.println("求和结果:" + sum);
        // 流为空时,返回初始值0
        Integer emptySum = List.<Integer>of().stream().reduce(0, Integer::sum);
        System.out.println("空流求和结果:" + emptySum);

        // 4. 两个参数的reduce:求乘积
        System.out.println("=====两个参数reduce求乘积=====");
        Integer product = numList.stream().reduce(1, (a, b) -> a * b);
        System.out.println("乘积结果:" + product);

        // 5. map-reduce模式:求所有用户的薪资总和
        System.out.println("=====map-reduce求薪资总和=====");
        Double totalSalary = userList.stream()
                .map(User::getSalary)
                .reduce(0.0, Double::sum);
        System.out.println("薪资总和:" + totalSalary);

        // 6. map-reduce模式:拼接所有用户名
        System.out.println("=====map-reduce拼接用户名=====");
        String nameStr = userList.stream()
                .map(User::getName)
                .reduce("", (a, b) -> a + "、" + b);
        System.out.println("用户名拼接:" + nameStr);

        // 7. 三个参数的reduce:并行流计算薪资总和
        System.out.println("=====三个参数reduce并行流计算=====");
        Double parallelTotalSalary = userList.parallelStream()
                .reduce(0.0,
                        (sumResult, user) -> sumResult + user.getSalary(), // 累加器:每个线程内的计算
                        Double::sum); // 组合器:合并多个线程的结果
        System.out.println("并行流薪资总和:" + parallelTotalSalary);
    }
}

3. 练习题

  1. 使用reduce求1-20的整数和,要求使用两个参数的重载方法。
  2. 使用reduce求List<Integer> nums = Arrays.asList(12, 5, 28, 17, 33, 9)中的最小值,使用一个参数的重载方法。
  3. 使用map-reduce模式,求User列表中所有男性用户的年龄总和。
  4. 使用三个参数的reduce方法,并行流计算User列表中所有用户的薪资最大值。

4. 练习题答案

java 复制代码
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;

public class Day6ExerciseAnswer {
    public static void main(String[] args) {
        // 第1题
        System.out.println("=====第1题答案=====");
        Integer sum = Stream.iterate(1, n -> n + 1)
                .limit(20)
                .reduce(0, Integer::sum);
        System.out.println("1-20的和:" + sum);

        // 第2题
        System.out.println("=====第2题答案=====");
        List<Integer> nums = Arrays.asList(12, 5, 28, 17, 33, 9);
        Optional<Integer> minOptional = nums.stream().reduce(Integer::min);
        minOptional.ifPresent(min -> System.out.println("最小值:" + min));

        // 第3题
        System.out.println("=====第3题答案=====");
        List<User> userList = Arrays.asList(
                new User(1L, "张三", 22, "男", 1L, "研发部", 8000, "13800138000"),
                new User(2L, "李四", 28, "女", 1L, "研发部", 12000, "13800138001"),
                new User(3L, "王五", 35, "男", 2L, "产品部", 15000, "13800138002"),
                new User(4L, "赵六", 28, "男", 2L, "产品部", 12000, "13800138003"),
                new User(5L, "孙七", 30, "女", 3L, "人事部", 9000, "13800138004")
        );
        Integer maleAgeSum = userList.stream()
                .filter(user -> "男".equals(user.getGender()))
                .map(User::getAge)
                .reduce(0, Integer::sum);
        System.out.println("男性用户年龄总和:" + maleAgeSum);

        // 第4题
        System.out.println("=====第4题答案=====");
        Double maxSalary = userList.parallelStream()
                .reduce(Double.MIN_VALUE,
                        (currentMax, user) -> Math.max(currentMax, user.getSalary()),
                        Math::max);
        System.out.println("薪资最大值:" + maxSalary);
    }
}

Day7:核心收集器Collector与Collectors工具类(基础篇)

1. 知识点

  1. Collector核心作用 :Stream的终止操作,将流中的元素收集到可变容器中(List、Set、Map等),是日常开发中最常用的Stream终止操作,比reduce更适合容器收集场景。
  2. Collectors工具类:JDK提供的Collector工厂类,内置了大量常用的收集器实现,无需自定义Collector即可完成绝大多数场景的收集需求。
  3. 基础集合收集器
API 作用 JDK版本 特性
Collectors.toList() 收集到List集合 JDK8+ 返回的List具体类型不固定,JDK8是ArrayList,可修改
Stream.toList() 收集到List集合 JDK16+ 返回不可变List,不支持add/remove,性能更优
Collectors.toSet() 收集到Set集合,自动去重 JDK8+ 返回的Set具体类型不固定,JDK8是HashSet
Collectors.toCollection(Supplier<C> collectionFactory) 收集到指定类型的集合 JDK8+ 可自定义集合类型,比如LinkedList、TreeSet
  1. Map收集器

    1. 核心API:Collectors.toMap(Function keyMapper, Function valueMapper, BinaryOperator mergeFunction, Supplier mapFactory)

    2. 核心参数:

      • keyMapper:Map的key生成规则
      • valueMapper:Map的value生成规则
      • mergeFunction:key重复时的合并规则,不指定时key重复会抛出IllegalStateException
      • mapFactory:自定义Map的具体类型,比如TreeMap、LinkedHashMap
  2. 简单聚合&字符串拼接收集器

API 作用
Collectors.counting() 统计元素个数,等价于stream.count()
Collectors.summingInt/Long/Double() 求和,等价于mapToXxx().sum()
Collectors.averagingInt/Long/Double() 求平均值
Collectors.maxBy()/minBy() 求最大值/最小值
Collectors.joining() 字符串拼接,支持分隔符、前缀、后缀

2. 示例代码

less 复制代码
import java.util.*;
import java.util.stream.Collectors;

public class Day7CollectorDemo {
    public static void main(String[] args) {
        List<Integer> numList = Arrays.asList(1, 2, 2, 3, 4, 4, 5);
        List<User> userList = Arrays.asList(
                new User(1L, "张三", 22, "男", 1L, "研发部", 8000, "13800138000"),
                new User(2L, "李四", 28, "女", 1L, "研发部", 12000, "13800138001"),
                new User(3L, "王五", 35, "男", 2L, "产品部", 15000, "13800138002"),
                new User(4L, "赵六", 28, "男", 2L, "产品部", 12000, "13800138003"),
                new User(2L, "李四", 28, "女", 1L, "研发部", 12000, "13800138001")
        );

        // 1. 基础集合收集
        System.out.println("=====基础集合收集=====");
        // toList
        List<Integer> listResult = numList.stream().filter(n -> n > 2).collect(Collectors.toList());
        System.out.println("toList结果:" + listResult);

        // toSet 自动去重
        Set<Integer> setResult = numList.stream().filter(n -> n > 2).collect(Collectors.toSet());
        System.out.println("toSet结果:" + setResult);

        // toCollection 自定义集合类型
        LinkedList<Integer> linkedListResult = numList.stream()
                .filter(n -> n > 2)
                .collect(Collectors.toCollection(LinkedList::new));
        System.out.println("toCollection LinkedList结果:" + linkedListResult);

        TreeSet<Integer> treeSetResult = numList.stream()
                .collect(Collectors.toCollection(TreeSet::new));
        System.out.println("toCollection TreeSet结果:" + treeSetResult);

        // 2. Map收集
        System.out.println("=====Map收集=====");
        // 基础toMap:id -> User,处理key重复
        Map<Long, User> userMap = userList.stream()
                .distinct()
                .collect(Collectors.toMap(
                        User::getId, // key:用户ID
                        user -> user, // value:用户对象
                        (oldValue, newValue) -> oldValue // key重复时,保留旧值
                ));
        System.out.println("用户Map的key:" + userMap.keySet());

        // toMap:id -> 用户名
        Map<Long, String> idNameMap = userList.stream()
                .distinct()
                .collect(Collectors.toMap(User::getId, User::getName));
        System.out.println("id-用户名Map:" + idNameMap);

        // 3. 字符串拼接
        System.out.println("=====字符串拼接=====");
        // 基础拼接
        String nameJoin = userList.stream()
                .map(User::getName)
                .distinct()
                .collect(Collectors.joining("、"));
        System.out.println("用户名拼接:" + nameJoin);

        // 带前缀、后缀的拼接
        String nameJoinWithFix = userList.stream()
                .map(User::getName)
                .distinct()
                .collect(Collectors.joining("、", "【", "】"));
        System.out.println("带前后缀的拼接:" + nameJoinWithFix);

        // 4. 简单聚合
        System.out.println("=====简单聚合=====");
        // 求平均薪资
        Double avgSalary = userList.stream()
                .collect(Collectors.averagingDouble(User::getSalary));
        System.out.println("平均薪资:" + avgSalary);

        // 求薪资总和
        Double sumSalary = userList.stream()
                .collect(Collectors.summingDouble(User::getSalary));
        System.out.println("薪资总和:" + sumSalary);
    }
}

3. 练习题

  1. 对User列表,过滤出研发部的用户,将用户名收集到一个不可变的List中(兼容JDK8和JDK16+两种方式)。
  2. 对User列表,转换为Map<Long, Double>,key是用户ID,value是用户薪资,若ID重复,取薪资更高的value。
  3. List<String> wordList = Arrays.asList("Java", "Stream", "Lambda", "Spring", "MyBatis"),将字符串用逗号拼接,前缀为"技术栈:",后缀为"。",输出最终字符串。
  4. 对1-100的整数流,过滤出能被5整除的数,收集到TreeSet中,输出结果。

4. 练习题答案

typescript 复制代码
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Day7ExerciseAnswer {
    public static void main(String[] args) {
        List<User> userList = Arrays.asList(
                new User(1L, "张三", 22, "男", 1L, "研发部", 8000, "13800138000"),
                new User(2L, "李四", 28, "女", 1L, "研发部", 12000, "13800138001"),
                new User(3L, "王五", 35, "男", 2L, "产品部", 15000, "13800138002"),
                new User(4L, "赵六", 28, "男", 2L, "产品部", 12000, "13800138003"),
                new User(2L, "李四", 28, "女", 1L, "研发部", 13000, "13800138001")
        );

        // 第1题
        System.out.println("=====第1题答案=====");
        // JDK8+ 方式:通过Collections.unmodifiableList包装
        List<String> jdk8NameList = userList.stream()
                .filter(user -> "研发部".equals(user.getDeptName()))
                .map(User::getName)
                .collect(Collectors.collectingAndThen(
                        Collectors.toList(),
                        Collections::unmodifiableList
                ));
        System.out.println("JDK8不可变List:" + jdk8NameList);

        // JDK16+ 方式:Stream.toList()
        List<String> jdk16NameList = userList.stream()
                .filter(user -> "研发部".equals(user.getDeptName()))
                .map(User::getName)
                .toList();
        System.out.println("JDK16+不可变List:" + jdk16NameList);

        // 第2题
        System.out.println("=====第2题答案=====");
        Map<Long, Double> idSalaryMap = userList.stream()
                .collect(Collectors.toMap(
                        User::getId,
                        User::getSalary,
                        (oldSalary, newSalary) -> Math.max(oldSalary, newSalary)
                ));
        System.out.println("ID-薪资Map:" + idSalaryMap);

        // 第3题
        System.out.println("=====第3题答案=====");
        List<String> wordList = Arrays.asList("Java", "Stream", "Lambda", "Spring", "MyBatis");
        String result = wordList.stream()
                .collect(Collectors.joining(", ", "技术栈:", "。"));
        System.out.println(result);

        // 第4题
        System.out.println("=====第4题答案=====");
        TreeSet<Integer> treeSetResult = Stream.iterate(1, n -> n + 1)
                .limit(100)
                .filter(n -> n % 5 == 0)
                .collect(Collectors.toCollection(TreeSet::new));
        System.out.println("TreeSet结果:" + treeSetResult);
    }
}

Day8:高级收集器与下游收集器(进阶篇)

1. 知识点

  1. 分组收集器groupingBy

    1. 核心作用:按指定的分类规则,将流中的元素分组,返回Map<K, List<T>>,是SQL中group by的Java实现

    2. 核心重载方法:

      • Collectors.groupingBy(Function classifier):单参数,仅指定分组规则,默认将分组后的元素收集到List中
      • Collectors.groupingBy(Function classifier, Collector downstream):双参数,指定分组规则+下游收集器,对分组后的元素做二次聚合/收集
      • Collectors.groupingBy(Function classifier, Supplier mapFactory, Collector downstream):三参数,自定义Map的类型+分组规则+下游收集器
  2. 分区收集器partitioningBy

    1. 核心作用:特殊的分组,分类器是Predicate断言,返回Map<Boolean, List<T>>,只有true和false两个分区

    2. 重载方法:

      • Collectors.partitioningBy(Predicate predicate):单参数,默认收集到List中
      • Collectors.partitioningBy(Predicate predicate, Collector downstream):双参数,支持下游收集器
  3. 下游收集器

    1. 核心作用:对分组/分区后的子流,进行二次收集、聚合、转换,实现多级统计
    2. 常用下游收集器:
    下游收集器 作用
    Collectors.counting() 统计分组内的元素个数
    Collectors.summingXxx()/averagingXxx() 分组内求和/求平均值
    Collectors.maxBy()/minBy() 分组内求最大值/最小值
    Collectors.mapping() 对分组内的元素先做映射,再做收集
    Collectors.groupingBy() 嵌套分组,实现多级分组
    Collectors.collectingAndThen() 收集完成后,对结果执行转换操作
  4. 自定义Collector入门

    1. 核心API:Collector.of(Supplier supplier, BiConsumer accumulator, BinaryOperator combiner, Function finisher, Characteristics... characteristics)

    2. 核心参数:

      • supplier:创建结果容器的工厂
      • accumulator:累加器,将元素添加到容器中
      • combiner:组合器,并行流中合并多个容器
      • finisher:完成器,对容器做最终转换
      • characteristics:特性标记,比如CONCURRENT、UNORDERED、IDENTITY_FINISH

2. 示例代码

less 复制代码
import java.util.*;
import java.util.stream.Collectors;

public class Day8AdvancedCollectorDemo {
    public static void main(String[] args) {
        List<User> userList = Arrays.asList(
                new User(1L, "张三", 22, "男", 1L, "研发部", 8000, "13800138000"),
                new User(2L, "李四", 28, "女", 1L, "研发部", 12000, "13800138001"),
                new User(3L, "王五", 35, "男", 2L, "产品部", 15000, "13800138002"),
                new User(4L, "赵六", 28, "男", 2L, "产品部", 12000, "13800138003"),
                new User(5L, "孙七", 30, "女", 3L, "人事部", 9000, "13800138004"),
                new User(6L, "周八", 32, "男", 3L, "人事部", 11000, "13800138005"),
                new User(7L, "吴九", 26, "女", 1L, "研发部", 10000, "13800138006")
        );

        // 1. 基础分组:按部门分组
        System.out.println("=====按部门分组=====");
        Map<String, List<User>> deptUserMap = userList.stream()
                .collect(Collectors.groupingBy(User::getDeptName));
        deptUserMap.forEach((deptName, users) -> System.out.println(deptName + ":" + users.size() + "人"));

        // 2. 分组+下游收集器:按部门统计人数
        System.out.println("=====按部门统计人数=====");
        Map<String, Long> deptCountMap = userList.stream()
                .collect(Collectors.groupingBy(
                        User::getDeptName,
                        Collectors.counting() // 下游收集器:统计个数
                ));
        System.out.println("部门人数统计:" + deptCountMap);

        // 3. 分组+下游聚合:按部门统计平均薪资
        System.out.println("=====按部门统计平均薪资=====");
        Map<String, Double> deptAvgSalaryMap = userList.stream()
                .collect(Collectors.groupingBy(
                        User::getDeptName,
                        Collectors.averagingDouble(User::getSalary)
                ));
        deptAvgSalaryMap.forEach((deptName, avgSalary) -> System.out.println(deptName + "平均薪资:" + avgSalary));

        // 4. 多级分组:先按部门,再按性别分组
        System.out.println("=====多级分组:部门+性别=====");
        Map<String, Map<String, List<User>>> deptGenderMap = userList.stream()
                .collect(Collectors.groupingBy(
                        User::getDeptName,
                        Collectors.groupingBy(User::getGender) // 下游收集器:二次分组
                ));
        deptGenderMap.forEach((deptName, genderMap) -> {
            System.out.println(deptName + ":");
            genderMap.forEach((gender, users) -> System.out.println("  " + gender + ":" + users.size() + "人"));
        });

        // 5. 分组+mapping下游收集器:按部门收集所有用户名
        System.out.println("=====按部门收集用户名=====");
        Map<String, List<String>> deptNameMap = userList.stream()
                .collect(Collectors.groupingBy(
                        User::getDeptName,
                        Collectors.mapping(User::getName, Collectors.toList())
                ));
        System.out.println("部门-用户名Map:" + deptNameMap);

        // 6. 分区:按薪资是否大于10000分区
        System.out.println("=====按薪资分区=====");
        Map<Boolean, List<User>> salaryPartitionMap = userList.stream()
                .collect(Collectors.partitioningBy(user -> user.getSalary() > 10000));
        System.out.println("薪资大于10000的人数:" + salaryPartitionMap.get(true).size());
        System.out.println("薪资小于等于10000的人数:" + salaryPartitionMap.get(false).size());

        // 7. 分区+下游收集器:按薪资分区,统计男女比例
        System.out.println("=====分区+下游统计=====");
        Map<Boolean, Map<String, Long>> partitionGenderMap = userList.stream()
                .collect(Collectors.partitioningBy(
                        user -> user.getSalary() > 10000,
                        Collectors.groupingBy(User::getGender, Collectors.counting())
                ));
        System.out.println("薪资大于10000的性别统计:" + partitionGenderMap.get(true));
    }
}

3. 练习题

  1. 对User列表,按性别分组,统计每个性别的用户总人数和平均薪资。
  2. 对User列表,按年龄区间分区(年龄>=30为true,<30为false),分别统计两个区间的最高薪资和最低薪资。
  3. 对User列表,先按部门分组,再按薪资是否大于10000进行二次分组,统计每个二级分组的用户人数。
  4. 对User列表,按部门分组,收集每个部门薪资最高的用户信息,返回Map<String, User>

4. 练习题答案

less 复制代码
import java.util.*;
import java.util.stream.Collectors;

public class Day8ExerciseAnswer {
    public static void main(String[] args) {
        List<User> userList = Arrays.asList(
                new User(1L, "张三", 22, "男", 1L, "研发部", 8000, "13800138000"),
                new User(2L, "李四", 28, "女", 1L, "研发部", 12000, "13800138001"),
                new User(3L, "王五", 35, "男", 2L, "产品部", 15000, "13800138002"),
                new User(4L, "赵六", 28, "男", 2L, "产品部", 12000, "13800138003"),
                new User(5L, "孙七", 30, "女", 3L, "人事部", 9000, "13800138004"),
                new User(6L, "周八", 32, "男", 3L, "人事部", 11000, "13800138005"),
                new User(7L, "吴九", 26, "女", 1L, "研发部", 10000, "13800138006")
        );

        // 第1题
        System.out.println("=====第1题答案=====");
        Map<String, Map<String, Object>> genderStatMap = userList.stream()
                .collect(Collectors.groupingBy(
                        User::getGender,
                        Collectors.collectingAndThen(
                                Collectors.toList(),
                                users -> {
                                    Map<String, Object> stat = new HashMap<>();
                                    stat.put("总人数", (long) users.size());
                                    stat.put("平均薪资", users.stream().collect(Collectors.averagingDouble(User::getSalary)));
                                    return stat;
                                }
                        )
                ));
        genderStatMap.forEach((gender, stat) -> System.out.println(gender + ":" + stat));

        // 第2题
        System.out.println("=====第2题答案=====");
        Map<Boolean, Map<String, Double>> agePartitionStatMap = userList.stream()
                .collect(Collectors.partitioningBy(
                        user -> user.getAge() >= 30,
                        Collectors.collectingAndThen(
                                Collectors.toList(),
                                users -> {
                                    Map<String, Double> stat = new HashMap<>();
                                    stat.put("最高薪资", users.stream().mapToDouble(User::getSalary).max().orElse(0));
                                    stat.put("最低薪资", users.stream().mapToDouble(User::getSalary).min().orElse(0));
                                    return stat;
                                }
                        )
                ));
        agePartitionStatMap.forEach((isGt30, stat) -> System.out.println("年龄>=30:" + isGt30 + ",薪资统计:" + stat));

        // 第3题
        System.out.println("=====第3题答案=====");
        Map<String, Map<Boolean, Long>> deptSalaryCountMap = userList.stream()
                .collect(Collectors.groupingBy(
                        User::getDeptName,
                        Collectors.partitioningBy(
                                user -> user.getSalary() > 10000,
                                Collectors.counting()
                        )
                ));
        deptSalaryCountMap.forEach((deptName, partitionMap) -> {
            System.out.println(deptName + ":");
            partitionMap.forEach((isGt10000, count) -> System.out.println("  薪资>10000:" + isGt10000 + ",人数:" + count));
        });

        // 第4题
        System.out.println("=====第4题答案=====");
        Map<String, User> deptMaxSalaryUserMap = userList.stream()
                .collect(Collectors.groupingBy(
                        User::getDeptName,
                        Collectors.collectingAndThen(
                                Collectors.maxBy(Comparator.comparingDouble(User::getSalary)),
                                Optional::get
                        )
                ));
        deptMaxSalaryUserMap.forEach((deptName, user) ->
                System.out.println(deptName + "薪资最高用户:" + user.getName() + ",薪资:" + user.getSalary()));
    }
}

Day9:并行流、性能优化与避坑指南

1. 知识点

  1. 并行流底层原理

    1. 并行流基于JDK7引入的Fork/Join框架实现,采用「分而治之」的思想:将大任务拆分为多个小任务,分配到多个线程并行执行,最终合并所有小任务的结果
    2. 核心线程池:默认使用ForkJoinPool.commonPool(),默认线程数等于CPU核心数,可通过JVM参数-Djava.util.concurrent.ForkJoinPool.common.parallelism=N修改
    3. 并行流创建方式:collection.parallelStream()stream().parallel()
    4. 串行流转并行:stream().parallel();并行流转串行:stream().sequential()(流的最终模式以最后一次调用为准)
  2. 并行流适用与不适用场景

适用场景 不适用场景
CPU密集型任务(大量计算,无IO阻塞) IO密集型任务(文件读写、网络请求,线程会阻塞,并行效率低)
数据量足够大,计算成本高于任务拆分成本 小数据量任务(任务拆分的开销大于并行计算的收益)
无状态、纯函数式操作(计算仅依赖当前元素,无共享变量) 有状态的操作(distinct、sorted、limit、skip,并行需要同步,性能差)
数据源可轻松拆分(ArrayList、数组,支持随机访问) 数据源难拆分(LinkedList、Stream.iterate无限流)
  1. Stream高频避坑指南

    1. 流的一次性消费 :流执行终止操作后会被关闭,重复使用会抛出IllegalStateException: stream has already been operated upon or closed
    2. 惰性求值陷阱:不调用终止操作,所有中间操作永远不会执行,禁止在中间操作中执行业务逻辑
    3. 并行流线程安全问题:禁止在并行流中使用非线程安全的容器(比如ArrayList)做累加,会出现数据丢失/异常,必须使用collect收集器或线程安全的容器
    4. 自动装箱拆箱性能损耗 :大量数值计算时,优先使用IntStream/LongStream/DoubleStream原始类型流,避免包装类的频繁拆装箱
    5. peek误用:peek仅用于调试,禁止用peek修改元素状态或执行业务逻辑,并行流中peek执行顺序不可控
    6. toMap的key重复问题:不指定mergeFunction时,key重复会直接抛出异常,生产环境必须处理key重复场景
    7. 不修改源数据原则:禁止在Stream操作中修改源集合的元素或结构,会导致不可预期的异常
  2. Stream性能优化核心技巧

    1. 提前过滤:在流水线最前端执行filter操作,减少后续处理的数据量
    2. 优先使用原始类型流:避免自动装箱拆箱的性能开销
    3. 减少有状态的中间操作:尽量避免在大数据量流中使用distinct、sorted等有状态操作
    4. 合理使用并行流:仅在CPU密集型、大数据量、易拆分的场景使用并行流
    5. 避免过度嵌套:复杂的分组/映射操作尽量拆分,提升可读性和维护性

2. 示例代码

java 复制代码
import java.util.*;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class Day9ParallelStreamDemo {
    public static void main(String[] args) {
        // 1. 并行流基础使用
        System.out.println("=====并行流基础使用=====");
        List<Integer> numList = IntStream.rangeClosed(1, 10).boxed().toList();
        numList.parallelStream().forEach(n -> System.out.println(Thread.currentThread().getName() + ":" + n));

        // 2. 串行流vs并行流性能对比
        System.out.println("=====性能对比=====");
        long count = 100000000L;

        // 串行流计算
        long startSerial = System.currentTimeMillis();
        long sumSerial = LongStream.rangeClosed(1, count).reduce(0, Long::sum);
        long endSerial = System.currentTimeMillis();
        System.out.println("串行流计算结果:" + sumSerial + ",耗时:" + (endSerial - startSerial) + "ms");

        // 并行流计算
        long startParallel = System.currentTimeMillis();
        long sumParallel = LongStream.rangeClosed(1, count).parallel().reduce(0, Long::sum);
        long endParallel = System.currentTimeMillis();
        System.out.println("并行流计算结果:" + sumParallel + ",耗时:" + (endParallel - startParallel) + "ms");

        // 3. 自定义ForkJoinPool执行并行流
        System.out.println("=====自定义线程池执行并行流=====");
        ForkJoinPool customPool = new ForkJoinPool(4);
        try {
            long customSum = customPool.submit(() ->
                    LongStream.rangeClosed(1, 10000000L).parallel().sum()
            ).get();
            System.out.println("自定义线程池计算结果:" + customSum);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            customPool.shutdown();
        }

        // 4. 避坑示例1:并行流线程安全问题
        System.out.println("=====避坑1:并行流线程安全问题=====");
        // 错误写法:非线程安全的ArrayList,并行添加会出现数据丢失
        List<Integer> wrongList = new ArrayList<>();
        IntStream.rangeClosed(1, 1000).parallel().forEach(wrongList::add);
        System.out.println("错误写法,预期1000个元素,实际:" + wrongList.size());

        // 正确写法:使用collect收集器,线程安全
        List<Integer> rightList = IntStream.rangeClosed(1, 1000)
                .parallel()
                .boxed()
                .collect(Collectors.toList());
        System.out.println("正确写法,预期1000个元素,实际:" + rightList.size());

        // 5. 避坑示例2:惰性求值陷阱
        System.out.println("=====避坑2:惰性求值陷阱=====");
        Stream<Integer> stream = IntStream.rangeClosed(1, 5).boxed()
                .peek(n -> System.out.println("执行peek:" + n));
        System.out.println("此时终止操作未调用,peek不会执行");
        // 调用终止操作,才会执行中间操作
        stream.count();
    }
}

3. 练习题

  1. 对比串行流和并行流,计算1-100000000的所有整数的平方和,分别输出计算结果和执行耗时。
  2. 修复以下代码的线程安全问题:
rust 复制代码
List<String> result = new ArrayList<>();
Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h").parallelStream()
        .map(String::toUpperCase)
        .forEach(result::add);
  1. 写一个示例,验证Stream的惰性求值特性:中间操作中包含打印语句,不调用终止操作时,打印语句不会执行。
  2. 自定义一个并行度为6的ForkJoinPool,执行并行流计算1-1000000的累加和,输出结果。

4. 练习题答案

java 复制代码
import java.util.*;
import java.util.concurrent.ForkJoinPool;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.LongStream;

public class Day9ExerciseAnswer {
    public static void main(String[] args) {
        // 第1题
        System.out.println("=====第1题答案=====");
        long maxNum = 100000000L;

        // 串行流
        long startSerial = System.currentTimeMillis();
        long serialResult = LongStream.rangeClosed(1, maxNum)
                .map(n -> n * n)
                .sum();
        long endSerial = System.currentTimeMillis();
        System.out.println("串行流结果:" + serialResult + ",耗时:" + (endSerial - startSerial) + "ms");

        // 并行流
        long startParallel = System.currentTimeMillis();
        long parallelResult = LongStream.rangeClosed(1, maxNum)
                .parallel()
                .map(n -> n * n)
                .sum();
        long endParallel = System.currentTimeMillis();
        System.out.println("并行流结果:" + parallelResult + ",耗时:" + (endParallel - startParallel) + "ms");

        // 第2题:修复线程安全问题
        System.out.println("=====第2题答案=====");
        // 正确写法1:使用collect收集器(推荐)
        List<String> rightResult1 = Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h").parallelStream()
                .map(String::toUpperCase)
                .collect(Collectors.toList());
        System.out.println("正确写法1结果:" + rightResult1 + ",元素个数:" + rightResult1.size());

        // 正确写法2:使用线程安全的容器
        List<String> rightResult2 = Collections.synchronizedList(new ArrayList<>());
        Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h").parallelStream()
                .map(String::toUpperCase)
                .forEach(rightResult2::add);
        System.out.println("正确写法2结果:" + rightResult2 + ",元素个数:" + rightResult2.size());

        // 第3题:验证惰性求值
        System.out.println("=====第3题答案=====");
        System.out.println("开始构建流,中间操作不会执行");
        IntStream stream = IntStream.rangeClosed(1, 3)
                .peek(n -> System.out.println("执行peek,元素:" + n))
                .map(n -> n * 2)
                .peek(n -> System.out.println("执行map后,元素:" + n));
        System.out.println("流构建完成,未调用终止操作,无打印输出");
        System.out.println("调用终止操作count(),开始执行中间操作:");
        stream.count();

        // 第4题:自定义ForkJoinPool
        System.out.println("=====第4题答案=====");
        ForkJoinPool customPool = new ForkJoinPool(6);
        try {
            long sum = customPool.submit(() ->
                    LongStream.rangeClosed(1, 1000000L).parallel().sum()
            ).get();
            System.out.println("自定义线程池计算结果:" + sum);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            customPool.shutdown();
        }
    }
}

Day10:综合实战与最佳实践

1. 知识点

  1. Stream流式编程最佳实践

    1. 保持Lambda简洁性:Lambda表达式尽量控制在1-3行,复杂逻辑抽离为单独的方法,使用方法引用替代Lambda,提升可读性
    2. 合理拆分长链式调用:避免过长的链式调用,将逻辑相关的操作拆分为多个步骤,每个步骤有明确的语义
    3. 提前过滤,减少数据量:将filter操作尽量放在流水线的最前端,减少后续操作的处理数据量,提升性能
    4. 空值安全处理:配合Optional处理可能为null的元素,避免在Stream中出现空指针异常
    5. 优先使用方法引用User::getNameuser -> user.getName() 可读性更高,优先使用
    6. 避免过度使用Stream:简单的for循环如果可读性和性能更好,就无需强行使用Stream,不要为了用而用
    7. IO流必须关闭Files.lines()等IO相关的流,必须使用try-with-resources语法,确保流关闭,避免资源泄漏
    8. 并行流谨慎使用:仅在CPU密集型、大数据量场景使用,使用前必须做性能测试,避免并行带来的线程安全问题
  2. Stream日常开发高频实战场景

    1. 数据清洗与转换:实体类DTO/VO转换、数据过滤、格式转换
    2. 分组聚合统计:类似SQL的group by操作,多维度数据统计
    3. 数据排序与TOP N:多条件排序、TOP N数据筛选
    4. 集合数据扁平化:嵌套集合、树形结构的扁平化处理
    5. 文件内容处理:按行读取文件、单词统计、内容过滤

2. 示例代码

less 复制代码
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

public class Day10PracticalDemo {
    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) {
        // 模拟订单数据
        List<Order> orderList = Arrays.asList(
                new Order(1L, 1L, "Java编程思想", 89.0, LocalDateTime.parse("2024-01-05 10:20:00", FORMATTER)),
                new Order(2L, 2L, "Spring实战", 79.0, LocalDateTime.parse("2024-01-15 14:30:00", FORMATTER)),
                new Order(3L, 1L, "MySQL必知必会", 59.0, LocalDateTime.parse("2024-02-02 09:10:00", FORMATTER)),
                new Order(4L, 3L, "Java编程思想", 89.0, LocalDateTime.parse("2024-02-10 16:40:00", FORMATTER)),
                new Order(5L, 2L, "Redis设计与实现", 69.0, LocalDateTime.parse("2024-03-05 11:20:00", FORMATTER)),
                new Order(6L, 1L, "Spring实战", 79.0, LocalDateTime.parse("2024-03-12 15:30:00", FORMATTER)),
                new Order(7L, 4L, "Java编程思想", 89.0, LocalDateTime.parse("2024-03-20 10:00:00", FORMATTER)),
                new Order(8L, 3L, "MySQL必知必会", 59.0, LocalDateTime.parse("2024-01-25 08:50:00", FORMATTER))
        );

        // 实战1:按月份分组,统计每个月的总销售额
        System.out.println("=====实战1:月度销售额统计=====");
        Map<String, Double> monthAmountMap = orderList.stream()
                .collect(Collectors.groupingBy(
                        order -> order.getOrderTime().getMonth().getValue() + "月",
                        Collectors.summingDouble(Order::getAmount)
                ));
        monthAmountMap.forEach((month, amount) -> System.out.println(month + "销售额:" + amount + "元"));

        // 实战2:统计商品销量TOP3
        System.out.println("=====实战2:商品销量TOP3=====");
        List<Map.Entry<String, Long>> goodsTop3 = orderList.stream()
                .collect(Collectors.groupingBy(Order::getGoodsName, Collectors.counting()))
                .entrySet().stream()
                .sorted(Map.Entry.<String, Long>comparingByValue().reversed())
                .limit(3)
                .toList();
        goodsTop3.forEach(entry -> System.out.println(entry.getKey() + ":销量" + entry.getValue() + "单"));

        // 实战3:统计消费金额最高的前3个用户
        System.out.println("=====实战3:用户消费TOP3=====");
        List<Map.Entry<Long, Double>> userTop3 = orderList.stream()
                .collect(Collectors.groupingBy(
                        Order::getUserId,
                        Collectors.summingDouble(Order::getAmount)
                ))
                .entrySet().stream()
                .sorted(Map.Entry.<Long, Double>comparingByValue().reversed())
                .limit(3)
                .toList();
        userTop3.forEach(entry -> System.out.println("用户ID:" + entry.getKey() + ",消费总额:" + entry.getValue() + "元"));
    }
}

3. 练习题

  1. 基于以下User列表,完成综合需求:
sql 复制代码
List<User> userList = Arrays.asList(
        new User(1L, "张三", 22, "男", 1L, "研发部", 8000, "13800138000"),
        new User(2L, "李四", 28, "女", 1L, "研发部", 12000, "13800138001"),
        new User(3L, "王五", 35, "男", 2L, "产品部", 15000, "13800138002"),
        new User(4L, "赵六", 28, "男", 2L, "产品部", 12000, "13800138003"),
        new User(5L, "孙七", 30, "女", 3L, "人事部", 9000, "13800138004"),
        new User(6L, "周八", 32, "男", 3L, "人事部", 11000, "13800138005"),
        new User(7L, "吴九", 26, "女", 1L, "研发部", 10000, "13800138006"),
        new User(8L, "郑十", 33, "男", 2L, "产品部", 18000, "13800138007")
);
  1. 需求: a. 统计每个部门的总人数、平均薪资、最高薪资、最低薪资 b. 按薪资降序排序,取薪资TOP3的用户,输出姓名、部门、薪资 c. 按年龄区间分组:20-25岁、26-30岁、31-35岁,统计每个区间的人数和男女比例
  2. 读取本地word.txt文件,完成需求: a. 忽略大小写和标点符号,统计文件中每个单词的出现次数 b. 按出现次数降序排序,取出现次数最多的前5个单词,输出单词和次数

4. 练习题答案

less 复制代码
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Day10ExerciseAnswer {
    public static void main(String[] args) {
        List<User> userList = Arrays.asList(
                new User(1L, "张三", 22, "男", 1L, "研发部", 8000, "13800138000"),
                new User(2L, "李四", 28, "女", 1L, "研发部", 12000, "13800138001"),
                new User(3L, "王五", 35, "男", 2L, "产品部", 15000, "13800138002"),
                new User(4L, "赵六", 28, "男", 2L, "产品部", 12000, "13800138003"),
                new User(5L, "孙七", 30, "女", 3L, "人事部", 9000, "13800138004"),
                new User(6L, "周八", 32, "男", 3L, "人事部", 11000, "13800138005"),
                new User(7L, "吴九", 26, "女", 1L, "研发部", 10000, "13800138006"),
                new User(8L, "郑十", 33, "男", 2L, "产品部", 18000, "13800138007")
        );

        // 第1题a:部门统计
        System.out.println("=====第1题a:部门统计=====");
        Map<String, Map<String, Object>> deptStatMap = userList.stream()
                .collect(Collectors.groupingBy(
                        User::getDeptName,
                        Collectors.collectingAndThen(
                                Collectors.toList(),
                                users -> {
                                    Map<String, Object> stat = new HashMap<>();
                                    stat.put("总人数", (long) users.size());
                                    stat.put("平均薪资", users.stream().collect(Collectors.averagingDouble(User::getSalary)));
                                    stat.put("最高薪资", users.stream().mapToDouble(User::getSalary).max().orElse(0));
                                    stat.put("最低薪资", users.stream().mapToDouble(User::getSalary).min().orElse(0));
                                    return stat;
                                }
                        )
                ));
        deptStatMap.forEach((deptName, stat) -> System.out.println(deptName + ":" + stat));

        // 第1题b:薪资TOP3
        System.out.println("=====第1题b:薪资TOP3=====");
        List<User> salaryTop3 = userList.stream()
                .sorted(Comparator.comparingDouble(User::getSalary).reversed())
                .limit(3)
                .toList();
        salaryTop3.forEach(user -> System.out.printf("姓名:%s,部门:%s,薪资:%.0f%n",
                user.getName(), user.getDeptName(), user.getSalary()));

        // 第1题c:年龄区间分组
        System.out.println("=====第1题c:年龄区间分组=====");
        Map<String, Map<String, Object>> ageRangeMap = userList.stream()
                .collect(Collectors.groupingBy(
                        user -> {
                            int age = user.getAge();
                            if (age >= 20 && age <= 25) return "20-25岁";
                            else if (age >= 26 && age <= 30) return "26-30岁";
                            else if (age >= 31 && age <= 35) return "31-35岁";
                            else return "其他";
                        },
                        Collectors.collectingAndThen(
                                Collectors.toList(),
                                users -> {
                                    Map<String, Object> stat = new HashMap<>();
                                    stat.put("总人数", (long) users.size());
                                    Map<String, Long> genderRatio = users.stream()
                                            .collect(Collectors.groupingBy(User::getGender, Collectors.counting()));
                                    stat.put("男女比例", genderRatio);
                                    return stat;
                                }
                        )
                ));
        ageRangeMap.forEach((range, stat) -> System.out.println(range + ":" + stat));

        // 第2题:文件单词统计
        System.out.println("=====第2题:文件单词统计=====");
        try (Stream<String> lines = Files.lines(Paths.get("word.txt"))) {
            List<Map.Entry<String, Long>> wordTop5 = lines
                    // 去除标点符号,转小写
                    .map(line -> line.replaceAll("[^a-zA-Z0-9\u4e00-\u9fa5\s]", "").toLowerCase())
                    // 拆分单词
                    .flatMap(line -> Arrays.stream(line.split("\s+")))
                    // 过滤空字符串
                    .filter(word -> !word.trim().isEmpty())
                    // 统计次数
                    .collect(Collectors.groupingBy(word -> word, Collectors.counting()))
                    .entrySet().stream()
                    // 排序
                    .sorted(Map.Entry.<String, Long>comparingByValue().reversed())
                    // 取TOP5
                    .limit(5)
                    .toList();
            System.out.println("出现次数TOP5的单词:");
            wordTop5.forEach(entry -> System.out.println(entry.getKey() + ":" + entry.getValue() + "次"));
        } catch (Exception e) {
            System.out.println("文件读取失败:" + e.getMessage());
        }
    }
}

学习后续建议

  1. 完成10天学习后,建议在日常开发中强制使用Stream替代传统的for循环,熟练掌握常用API
  2. 深入学习Stream底层原理,了解Spliterator、Fork/Join框架的实现细节
  3. 学习Optional的高级用法,配合Stream实现完全的空指针安全编程
  4. 学习Java 9+新增的Stream API增强(比如takeWhile、dropWhile、ofNullable等)
  5. 结合函数式接口、Lambda表达式,深入理解Java函数式编程的思想

需要我把这份计划整理成一份可直接复制运行的单文件完整代码包,并补充每日学习的自检清单吗?

相关推荐
摇滚侠2 小时前
Java 零基础全套视频教程,面向对象(进阶),笔记 90-103
java·开发语言·笔记
椰羊~王小美2 小时前
C、Java、Go、Python 对比
java·c语言
Gerardisite2 小时前
企业微信自动化开发新思路: RPA 接入方案
java·python·自动化·企业微信·rpa
小谢小哥2 小时前
49-缓存一致性详解
java·后端·架构
青槿吖2 小时前
Sentinel 进阶实战:Feign 整合 + 全局异常 + Nacos 持久化,生产环境直接用
java·开发语言·spring cloud·微服务·云原生·ribbon·sentinel
穿条秋裤到处跑2 小时前
每日一道leetcode(2026.04.21):执行交换操作后的最小汉明距离
java·算法·leetcode
疯狂打码的少年2 小时前
内存管理三雄对决:C、Java、Python 的堆区、栈区、常量区、静态区深度解析
java·c语言·python
Seven972 小时前
Tomcat组件管理源码详解
java
AI技术社区2 小时前
Claude Code源码分析之提示词工程
java·开发语言·ai·ai编程