Java流式编程:修仙界的"流水线炼丹术"

83 阅读7分钟

Java流式编程:修仙界的"流水线炼丹术"

各位道友们好,我是会编程的吕洞宾!今天咱们来聊聊Java流式编程(Stream API)——这玩意儿就像是修仙界的"流水线炼丹术",能把复杂的集合操作变得像流水一样顺畅!

什么是流式编程?

流式编程就像是修仙界的"炼丹流水线":你把原材料(数据)放进去,经过一系列加工步骤(中间操作),最后得到成品(结果)。整个过程行云流水,代码简洁得让人想哭!

// 传统炼丹法(繁琐)
List<String> immortals = Arrays.asList("吕洞宾", "何仙姑", "铁拐李");
List<String> result = new ArrayList<>();
for (String immortal : immortals) {
    if (immortal.length() > 2) {
        result.add("八仙:" + immortal);
    }
}

// 流式炼丹法(优雅)
List<String> result = immortals.stream()
    .filter(name -> name.length() > 2)
    .map(name -> "八仙:" + name)
    .collect(Collectors.toList());

流的三大特性

1. 流水线操作(像炼丹步骤)

List<String> pills = Arrays.asList("筑基丹", "金丹", "元婴丹", "化神丹");

// 完整的炼丹流水线
List<String> highQualityPills = pills.stream()           // 1. 开炉点火(创建流)
    .filter(pill -> pill.contains("丹"))                // 2. 筛选药材(过滤)
    .map(pill -> "极品" + pill)                         // 3. 精炼提纯(转换)
    .sorted()                                           // 4. 按品级排序(排序)
    .collect(Collectors.toList());                      // 5. 收丹入库(收集)

2. 内部迭代(自动炼丹)

// 传统方式:手动控制炼丹火候(外部迭代)
for (String pill : pills) {
    if (pill.length() > 2) {
        System.out.println(pill);
    }
}

// 流式:告诉系统要什么,系统自动炼丹(内部迭代)
pills.stream()
    .filter(pill -> pill.length() > 2)
    .forEach(System.out::println);

3. 只能消费一次(丹药不能重复用)

Stream<String> immortalStream = immortals.stream();

// 第一次使用:正常
immortalStream.forEach(System.out::println);

// 第二次使用:报错!流已关闭
// immortalStream.forEach(System.out::println); // IllegalStateException

流的创建方式

1. 从集合创建(最常见的开炉方式)

List<String> immortals = Arrays.asList("吕洞宾", "何仙姑");
Stream<String> stream1 = immortals.stream();        // 顺序流
Stream<String> stream2 = immortals.parallelStream(); // 并行流

2. 从数组创建(批量药材)

String[] pillArray = {"筑基丹", "金丹", "元婴丹"};
Stream<String> stream3 = Arrays.stream(pillArray);
Stream<String> stream4 = Stream.of("筑基丹", "金丹");

3. 生成无限流(仙丹无限供应)

// 无限生成随机仙丹
Stream<String> infinitePills = Stream.generate(() -> 
    "仙丹" + (int)(Math.random() * 100));

// 无限序列:1, 2, 3, 4...
Stream<Integer> numbers = Stream.iterate(1, n -> n + 1);

// 取前5个
List<Integer> firstFive = numbers.limit(5).collect(Collectors.toList());

中间操作:炼丹的加工步骤

1. filter() - 筛选药材

List<String> allPills = Arrays.asList("筑基丹", "废丹", "金丹", "劣质丹", "元婴丹");

// 只保留高品质丹药
List<String> goodPills = allPills.stream()
    .filter(pill -> !pill.contains("废") && !pill.contains("劣质"))
    .collect(Collectors.toList());
// 结果:["筑基丹", "金丹", "元婴丹"]

2. map() - 转换提纯

// 普通丹药升级为极品丹药
List<String> upgradedPills = allPills.stream()
    .map(pill -> "极品" + pill)
    .collect(Collectors.toList());
// 结果:["极品筑基丹", "极品金丹", "极品元婴丹"]

3. flatMap() - 药材展开(一炉多丹)

List<List<String>> batchPills = Arrays.asList(
    Arrays.asList("筑基丹", "金丹"),
    Arrays.asList("元婴丹", "化神丹")
);

// 把多个批次的丹药合并成一炉
List<String> allInOne = batchPills.stream()
    .flatMap(List::stream)  // 把每个小列表展开
    .collect(Collectors.toList());
// 结果:["筑基丹", "金丹", "元婴丹", "化神丹"]

4. sorted() - 按品级排序

List<String> pills = Arrays.asList("元婴丹", "筑基丹", "金丹");

// 按名称排序
List<String> sortedPills = pills.stream()
    .sorted()
    .collect(Collectors.toList());
// 结果:["金丹", "元婴丹", "筑基丹"]

// 自定义排序:按丹药名字长度
List<String> lengthSorted = pills.stream()
    .sorted((a, b) -> Integer.compare(a.length(), b.length()))
    .collect(Collectors.toList());
// 结果:["金丹", "元婴丹", "筑基丹"]

5. distinct() - 去重(剔除重复丹药)

List<String> duplicatePills = Arrays.asList("筑基丹", "金丹", "筑基丹", "元婴丹");

List<String> uniquePills = duplicatePills.stream()
    .distinct()
    .collect(Collectors.toList());
// 结果:["筑基丹", "金丹", "元婴丹"]

6. limit() 和 skip() - 分批次炼丹

List<String> allPills = Arrays.asList("丹1", "丹2", "丹3", "丹4", "丹5");

// 取前3个丹药
List<String> firstBatch = allPills.stream()
    .limit(3)
    .collect(Collectors.toList());
// 结果:["丹1", "丹2", "丹3"]

// 跳过前2个,取后面的
List<String> remaining = allPills.stream()
    .skip(2)
    .collect(Collectors.toList());
// 结果:["丹3", "丹4", "丹5"]

终端操作:收丹入库

1. forEach() - 逐个检查丹药

List<String> pills = Arrays.asList("筑基丹", "金丹", "元婴丹");

pills.stream()
    .forEach(pill -> System.out.println("检查丹药:" + pill));

2. collect() - 收集成品丹药

// 收集到List
List<String> pillList = pills.stream()
    .collect(Collectors.toList());

// 收集到Set(自动去重)
Set<String> pillSet = pills.stream()
    .collect(Collectors.toSet());

// 收集到Map(丹药名->长度)
Map<String, Integer> pillMap = pills.stream()
    .collect(Collectors.toMap(
        pill -> pill,           // key:丹药名
        pill -> pill.length()   // value:名字长度
    ));

3. reduce() - 丹药合成

List<Integer> powerLevels = Arrays.asList(100, 200, 300);

// 合成所有丹药的能量
int totalPower = powerLevels.stream()
    .reduce(0, (a, b) -> a + b); // 从0开始,逐个相加
// 结果:600

// 更简洁的写法
int total = powerLevels.stream().reduce(0, Integer::sum);

4. 统计操作:丹药质量检测

List<Integer> qualityScores = Arrays.asList(85, 92, 78, 96, 88);

// 各种统计信息
long count = qualityScores.stream().count();           // 数量:5
int max = qualityScores.stream().max(Integer::compare).get(); // 最高分:96
int min = qualityScores.stream().min(Integer::compare).get(); // 最低分:78
double average = qualityScores.stream().mapToInt(Integer::intValue).average().getAsDouble(); // 平均分:87.8

// 使用IntSummaryStatistics一次性获取所有统计
IntSummaryStatistics stats = qualityScores.stream()
    .mapToInt(Integer::intValue)
    .summaryStatistics();
System.out.println("数量:" + stats.getCount());
System.out.println("最高:" + stats.getMax());
System.out.println("最低:" + stats.getMin());
System.out.println("平均:" + stats.getAverage());
System.out.println("总和:" + stats.getSum());

5. 匹配检查:丹药合格检测

List<Integer> qualityScores = Arrays.asList(85, 92, 78, 96, 88);

boolean allPass = qualityScores.stream().allMatch(score -> score >= 60); // 全部合格:true
boolean anyPerfect = qualityScores.stream().anyMatch(score -> score == 100); // 有满分:false
boolean noneFail = qualityScores.stream().noneMatch(score -> score < 60); // 没有不及格:true

6. 查找操作:寻找特定丹药

List<String> pills = Arrays.asList("筑基丹", "金丹", "元婴丹");

Optional<String> found = pills.stream()
    .filter(pill -> pill.contains("金"))
    .findFirst(); // 找到第一个金丹

if (found.isPresent()) {
    System.out.println("找到金丹:" + found.get());
}

// 并行流中查找任意一个
Optional<String> anyGoldPill = pills.parallelStream()
    .filter(pill -> pill.contains("金"))
    .findAny();

数值流:专门处理数值的炼丹炉

基本数值流

List<String> pills = Arrays.asList("筑基丹", "金丹", "元婴丹");

// 转换为数值流,避免装箱拆箱开销
IntStream nameLengths = pills.stream()
    .mapToInt(String::length); // 直接返回int,不是Integer

// 统计操作更高效
int totalLength = nameLengths.sum();
double avgLength = pills.stream().mapToInt(String::length).average().orElse(0);

数值范围流

// 生成1到100的炼丹炉温度
IntStream temperatures = IntStream.range(1, 101); // 1-100,不包含101
IntStream inclusiveTemps = IntStream.rangeClosed(1, 100); // 1-100,包含100

并行流:多炉同时炼丹

使用并行流提升效率

List<String> bigPillList = // 假设有10万颗丹药

// 顺序炼丹(单炉)
long startTime = System.currentTimeMillis();
bigPillList.stream()
    .filter(pill -> pill.length() > 2)
    .count();
long sequentialTime = System.currentTimeMillis() - startTime;

// 并行炼丹(多炉同时进行)
startTime = System.currentTimeMillis();
bigPillList.parallelStream()
    .filter(pill -> pill.length() > 2)
    .count();
long parallelTime = System.currentTimeMillis() - startTime;

System.out.println("顺序时间:" + sequentialTime + "ms");
System.out.println("并行时间:" + parallelTime + "ms");

注意事项:并行流的坑

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// 错误示范:有状态的Lambda(并行时结果不确定)
int[] sum = {0};
numbers.parallelStream().forEach(n -> {
    sum[0] += n; // 竞态条件!结果可能不对
});

// 正确做法:使用reduce
int correctSum = numbers.parallelStream().reduce(0, Integer::sum);

实战案例:修仙管理系统

案例1:丹药库存管理

class Pill {
    String name;
    int quality;
    double price;
    
    Pill(String name, int quality, double price) {
        this.name = name;
        this.quality = quality;
        this.price = price;
    }
}

List<Pill> inventory = Arrays.asList(
    new Pill("筑基丹", 85, 100.0),
    new Pill("金丹", 92, 500.0),
    new Pill("废丹", 30, 10.0),
    new Pill("元婴丹", 96, 1000.0)
);

// 复杂的丹药分析
Map<String, Double> result = inventory.stream()
    .filter(pill -> pill.quality > 60)                    // 过滤合格丹药
    .sorted((p1, p2) -> Integer.compare(p2.quality, p1.quality)) // 按质量降序
    .limit(10)                                            // 取前10名
    .collect(Collectors.toMap(
        pill -> pill.name,                                // 丹药名为key
        pill -> pill.price * 1.1,                        // 涨价10%为value
        (oldValue, newValue) -> oldValue                 // 重复key处理
    ));

案例2:修仙者排行榜

class Cultivator {
    String name;
    int level;
    String sect;
    
    Cultivator(String name, int level, String sect) {
        this.name = name;
        this.level = level;
        this.sect = sect;
    }
}

List<Cultivator> cultivators = Arrays.asList(
    new Cultivator("吕洞宾", 99, "八仙"),
    new Cultivator("孙悟空", 95, "花果山"),
    new Cultivator("何仙姑", 88, "八仙"),
    new Cultivator("牛魔王", 92, "妖魔")
);

// 多条件分组统计
Map<String, List<Cultivator>> bySect = cultivators.stream()
    .collect(Collectors.groupingBy(c -> c.sect));

// 每个门派的平均等级
Map<String, Double> avgLevelBySect = cultivators.stream()
    .collect(Collectors.groupingBy(
        c -> c.sect,
        Collectors.averagingInt(c -> c.level)
    ));

// 分区:高手和普通修仙者
Map<Boolean, List<Cultivator>> partitioned = cultivators.stream()
    .collect(Collectors.partitioningBy(c -> c.level >= 90));

性能优化技巧

1. 使用基本类型流避免装箱

// 不好:有装箱开销
List<Integer> levels = Arrays.asList(1, 2, 3, 4, 5);
int sum = levels.stream().mapToInt(Integer::intValue).sum();

// 好:直接使用IntStream
IntStream levelStream = IntStream.range(1, 6);
int efficientSum = levelStream.sum();

2. 短路操作提升性能

List<String> bigList = // 大量数据

// 找到第一个满足条件的就停止
Optional<String> result = bigList.stream()
    .filter(item -> item.length() > 10)
    .findFirst(); // 短路操作,不会处理所有数据

3. 避免在流中执行昂贵操作

// 不好:在流中执行IO操作
List<String> result = fileNames.stream()
    .map(name -> readFromDatabase(name)) // 每次map都查询数据库
    .collect(Collectors.toList());

// 好:先批量获取数据,再用流处理
List<String> allData = readAllFromDatabase();
List<String> betterResult = allData.stream()
    .filter(data -> data.length() > 0)
    .collect(Collectors.toList());

常见错误和避坑指南

错误1:重复使用流

Stream<String> stream = immortals.stream();

// 第一次使用
stream.filter(name -> name.contains("吕")).count();

// 错误:流已关闭,不能再使用
// stream.filter(name -> name.contains("何")).count(); // IllegalStateException

错误2:在流中修改外部状态

List<String> result = new ArrayList<>();
List<String> immortals = Arrays.asList("吕洞宾", "何仙姑");

// 错误:在流中修改外部变量(线程不安全)
immortals.parallelStream().forEach(name -> {
    result.add(name); // 可能产生竞态条件
});

// 正确:使用collect收集结果
List<String> safeResult = immortals.stream()
    .collect(Collectors.toList());

错误3:忽略Optional的空值处理

List<String> emptyList = Arrays.asList();

// 错误:直接get()可能抛异常
// String first = emptyList.stream().findFirst().get(); // NoSuchElementException

// 正确:安全处理
String first = emptyList.stream()
    .findFirst()
    .orElse("默认值");

Optional<String> maybeFirst = emptyList.stream().findFirst();
if (maybeFirst.isPresent()) {
    String value = maybeFirst.get();
}

总结

流式编程就是Java世界的"炼丹流水线":

  • 创建流:开炉点火,准备原材料
  • 中间操作:筛选、转换、排序等加工步骤
  • 终端操作:收丹入库,得到最终结果

记住流式编程的心法:

  1. 不可变性:流不会修改源数据
  2. 懒加载:只有终端操作才会触发执行
  3. 一次性:流只能消费一次
  4. 内部迭代:你只需关心做什么,不用管怎么做

掌握了流式编程,你的代码就能像吕洞宾的剑法一样行云流水,优雅高效!