Stream API:从“循环地狱”到“流水线狂魔”,你的代码需要来一场工业革命!

106 阅读3分钟

(附:祖传for循环 vs Stream性能对决 + 并行流骚操作)


一、传统循环:代码界的“996福报”

场景:从100个商品里,挑出价格超过50块的,打八折后收集名字。

祖传for循环写法

List<String> result = new ArrayList<>();  
for (Product product : productList) {         // 这循环比老板的会议还长  
    if (product.getPrice() > 50) {            // 条件判断  
        product.setPrice(product.getPrice() * 0.8);  // 打折  
        result.add(product.getName());       // 收集名字  
    }  
}  
// 点评:这代码写的我仿佛在流水线上拧螺丝!  

Stream工业革命版

List<String> result = productList.stream()  
    .filter(p -> p.getPrice() > 50)       // 过滤:只留贵的  
    .peek(p -> p.setPrice(p.getPrice() * 0.8))  // 打折:偷偷改价格  
    .map(Product::getName)                // 变形:只要名字  
    .collect(Collectors.toList());        // 打包:装车带走  
// 点评:这代码像坐上了自动化流水线,直接起飞!  

省流总结

  • 代码行数:7行 → 5行(看似差不多,但逼格翻倍)

  • 可读性:流水线式操作,逻辑直男式清晰

  • 逼格指数:从“搬砖工”晋升为“车间主任”


二、Stream核心操作:流水线上的“三大金刚”

1. filter(筛子)

// 筛出所有姓“张”的员工  
List<Employee> zhangs = employees.stream()  
    .filter(e -> e.getName().startsWith("张"))  
    .collect(Collectors.toList());  
// 点评:这筛子比大妈挑菜还严格!  

2. map(变形器)

// 把员工列表变成工资列表  
List<Double> salaries = employees.stream()  
    .map(Employee::getSalary)  
    .collect(Collectors.toList());  
// 点评:直接榨干员工,只留工资!  

3. collect(打包机)

// 把商品按类别分组  
Map<String, List<Product>> groupByCategory = products.stream()  
    .collect(Collectors.groupingBy(Product::getCategory));  
// 点评:分类打包,直接送去双十一仓库!  

骚操作

  • flatMap:把嵌套集合拍平(比如List<List<Integer>> → List<Integer>
  • sorted:排序(支持自定义Comparator,比老板的喜好还灵活)
  • limit / skip:分页操作(比食堂阿姨手抖还精准)

三、并行流:榨干CPU的“黑心工厂”

单线程Stream

long count = productList.stream()  
    .filter(p -> p.getPrice() > 100)  
    .count();  

并行流暴力版

long count = productList.parallelStream()  // 加个parallel,直接开多线程  
    .filter(p -> p.getPrice() > 100)  
    .count();  

性能真相(测试数据来自贴吧老哥的二手i7电脑):

数据量单线程Stream耗时并行流耗时结论
1万12ms25ms并行流启动反增耗时
100万150ms80ms并行开始有优势
1000万1450ms380ms并行流碾压

老哥忠告

  • 数据量小别用并行流,线程切换比干活还累!
  • 线程池默认用ForkJoinPool,小心和业务代码抢资源!

四、性能优化のののののののののののののののの玄学

1. 短路操作:用findFirst代替filter+limit

// 低效写法  
Optional<Product> product = products.stream()  
    .filter(p -> p.getPrice() > 100)  
    .limit(1)  
    .findFirst();  

// 高效写法(找到第一个直接跑路)  
Optional<Product> product = products.stream()  
    .filter(p -> p.getPrice() > 100)  
    .findFirst();  

2. 避免重复计算

// 错误示范:每次循环都计算  
products.stream()  
    .map(p -> p.getPrice() * exchangeRate)  // exchangeRate在循环外定义  
    .collect(Collectors.toList());  

// 正确姿势:预先计算变量  
double finalRate = exchangeRate;  
products.stream()  
    .map(p -> p.getPrice() * finalRate)  
    .collect(Collectors.toList());  

五、Stream作死行为大赏

1. 在Stream里修改外部变量

int sum = 0;  
products.stream()  
    .forEach(p -> sum += p.getPrice()); // 编译报错!变量必须是final或等效final  

正确姿势:用reduce求和!

2. 无限流作死

Stream.generate(() -> "加班!")  
    .forEach(System.out::println);  // 无限打印,直到电脑爆炸  

忠告:搭配limit(100)使用,保你狗命!


六、下期预告

《Optional类:从“连环判空”到“优雅防崩”,让你的代码告别NullPointerExceptionのののののの骚操作》
互动:评论区吐槽你最想用Stream暴改哪段祖传代码?点赞前三送学习资料!

(注:本文测试数据纯属贴吧老哥瞎编,实际效果可能因电脑配置、老板催促程度等因素波动)