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世界的"炼丹流水线":
- 创建流:开炉点火,准备原材料
- 中间操作:筛选、转换、排序等加工步骤
- 终端操作:收丹入库,得到最终结果
记住流式编程的心法:
- 不可变性:流不会修改源数据
- 懒加载:只有终端操作才会触发执行
- 一次性:流只能消费一次
- 内部迭代:你只需关心做什么,不用管怎么做
掌握了流式编程,你的代码就能像吕洞宾的剑法一样行云流水,优雅高效!