Day32 | Java Stream流式编程详解

在Java8之前,如果我们要处理集合数据通一般都是使用for循环、while循环和迭代器。

这种方式我们可以叫他命令式编程,要明确的告诉程序怎么做,每一步都要自己实现。

这是Java8之前的代码:

java 复制代码
package com.lazy.snail.day32;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * @ClassName Day32Demo
 * @Description TODO
 * @Author lazysnail
 * @Date 2025/7/14 11:30
 * @Version 1.0
 */
public class Day32Demo {
    public static void main(String[] args) {
        List<Transaction> transactions = getTransactions();
        List<Transaction> filteredTransactions = new ArrayList<>();
        for (Transaction t : transactions) {
            if (t.getValue() > 500) {
                filteredTransactions.add(t);
            }
        }
        Collections.sort(filteredTransactions, new Comparator<Transaction>() {
            @Override
            public int compare(Transaction t1, Transaction t2) {
                return Double.compare(t2.getValue(), t1.getValue());
            }
        });
    }

    public static List<Transaction> getTransactions() {
        List<Transaction> transactions = new ArrayList<>();
        transactions.add(new Transaction(450.0));
        transactions.add(new Transaction(1200.50));
        transactions.add(new Transaction(300.0));
        transactions.add(new Transaction(890.75));
        transactions.add(new Transaction(500.0));
        transactions.add(new Transaction(620.0));
        return transactions;
    }

}
class Transaction {
    double value;

    public Transaction(double value) {
        this.value = value;
    }

    public double getValue() {
        return value;
    }

    public void setValue(double value) {
        this.value = value;
    }
}

从交易数据列表中筛选出价格大于500的交易,并按金额排序。

筛选和排序的逻辑都要一步步的写出来。

java 复制代码
List<Transaction> sortedTransactions = transactions.stream()
    .filter(t -> t.getValue() > 500)
    .sorted(Comparator.comparing(Transaction::getValue).reversed())
    .toList();

现在用Stream方式直接过滤排序。

Stream用起来就像处理数据的工厂流水线,数据在流水线上经过一道道工序(过滤、映射、排序等),最终产出我们想要的结果。

一、什么是Stream?

首先,要明确Stream不是一种数据结构。它更像一个高级迭代器,从一个数据源(如集合、数组)获取元素,并支持复杂的聚合操作。

他有几个核心的特性:

不存储数据:流本身不存储任何元素。元素存储在底层的数据源里。

函数式本质:对流的操作会生成一个新的流,而不会修改它原始数据源。比如filter操作会生成一个只包含符合条件元素的新流。

惰性求值:流的中间操作(比如如filter, map)并不会马上执行。它们只有在遇到终端操作(像forEach, collect)的时候,才会一次性地、从头到尾地执行整个操作链。

只能消费一次:一个流一旦被终端操作消费过,就不能再被使用。如果需要再次处理,必须从数据源重新创建一个新的流。

比如:

java 复制代码
package com.lazy.snail.day32;

import java.util.List;
import java.util.stream.Stream;

/**
 * @ClassName Day32Demo2
 * @Description TODO
 * @Author lazysnail
 * @Date 2025/7/14 14:02
 * @Version 1.0
 */
public class Day32Demo2 {
    public static void main(String[] args) {
        List<String> list = List.of("懒惰蜗牛", "lazysnail", "lazysnailstudio.com");
        Stream<String> stream = list.stream();
        stream.forEach(System.out::println);
        stream.forEach(System.out::println);
    }
}

再次消费的时候就抛出异常了。

二、Stream的生命周期

一个完整的Stream操作通常分为三个步骤:创建 -> 中间操作 -> 终端操作。

2.1创建流

可以从多种数据源创建流:

从集合创建: list.stream(), set.parallelStream() (并行流)

从数组创建: Arrays.stream(array)

由值创建: Stream.of("a", "b", "c")

由函数创建(无限流):

Stream.iterate(T seed, UnaryOperator<T> f): 从一个种子值开始,不断应用函数f。

Stream.generate(Supplier<T> s): 不断调用get()方法来生成元素。

java 复制代码
package com.lazy.snail.day32;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

/**
 * @ClassName Day32Demo3
 * @Description TODO
 * @Author lazysnail
 * @Date 2025/7/14 14:08
 * @Version 1.0
 */
public class Day32Demo3 {
    public static void main(String[] args) {
        // 从集合创建
        List<String> names = List.of("懒惰蜗牛", "lazysnail");
        Stream<String> nameStream = names.stream();

        // 从数组创建
        String[] array = {"懒惰蜗牛", "lazysnail", "lazysnailstudio.com"};
        Stream<String> arrayStream = Arrays.stream(array);

        // 由值创建
        Stream<Integer> numberStream = Stream.of(1, 2, 3);

        // iterate 创建无限流: 0, 2, 4, 6... (需要配合limit使用)
        Stream<Integer> infiniteStream = Stream.iterate(0, n -> n + 2);
    }
}

2.2中间操作

中间操作是流水线上的加工站,每个操作都会返回一个新的流,可以链式调用。

常见的中间操作:

这个作为被操作数据源

java 复制代码
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 2, 4, 6, 8);

筛选与切片

filter(Predicate<T> p): 过滤出满足条件的元素。

筛选出所有的偶数

java 复制代码
List<Integer> evenNumbers = numbers.stream()
                .filter(n -> n % 2 == 0)
                .toList();

distinct(): 去除重复元素(基于equals()方法)。

取出重复数字

java 复制代码
List<Integer> distinctNumbers = numbers.stream()
                .distinct()
                .toList();

limit(long n): 截断流,使其元素不超过给定数量。

skip(long n): 跳过前n个元素。

java 复制代码
List<Integer> result = numbers.stream()
                .filter(n -> n % 2 == 0) 
                .distinct()             
                .skip(1)               
                .limit(2)               
                .toList();

结合上面四个操作,筛选偶数 -> 去重 -> 跳过第1个 -> 取前2个

映射

map(Function<T, R> f): 将每个元素转换为另一个元素。

把每个单词都转换成大写,或者获取长度。

java 复制代码
package com.lazy.snail.day32;

import java.util.List;

/**
 * @ClassName Day32Demo4
 * @Description TODO
 * @Author lazysnail
 * @Date 2025/7/14 14:19
 * @Version 1.0
 */
public class Day32Demo4 {
    public static void main(String[] args) {
        List<String> words = List.of("lazy", "snail", "lazysnailstudio.com");

        List<String> upperCaseWords = words.stream()
                .map(String::toUpperCase)
                .toList();
        System.out.println("大写单词: " + upperCaseWords);

        List<Integer> wordLengths = words.stream()
                .map(String::length)
                .toList();
        System.out.println("单词长度: " + wordLengths);
    }
}

flatMap(Function<T, Stream<R>> f): 将每个元素转换为一个流,然后将所有流连接成一个流(扁平化)。

把一个包含多个列表的列表,压平成一个单一的列表。

java 复制代码
package com.lazy.snail.day32;

import java.util.List;

/**
 * @ClassName Day32Demo5
 * @Description TODO
 * @Author lazysnail
 * @Date 2025/7/14 14:22
 * @Version 1.0
 */
public class Day32Demo5 {
    public static void main(String[] args) {
        List<List<Integer>> listOfLists = List.of(
                List.of(1, 2, 3),
                List.of(4, 5)
        );

        List<Integer> flatList = listOfLists.stream()
                .flatMap(list -> list.stream())
                .toList();

        System.out.println("扁平化后的列表: " + flatList);
    }
}

listOfLists是两个列表的列表,每个list元素被转换成一个新的stream,flatMap把这些stream连接成一个。

mapMulti(BiConsumer<T, Consumer<R>> mapper): (JDK 16+) flatMap的替代方案,在某些场景下更高效。它将每个元素替换为零个或多个元素。

某些场景下,要使用到mapMulti,要稍微比flatMap更灵活一点。

比如,把每个单词同时转换成大写和小写,然后放到同一个输出流里。

java 复制代码
package com.lazy.snail.day32;

import java.util.List;

/**
 * @ClassName Day32Demo6
 * @Description TODO
 * @Author lazysnail
 * @Date 2025/7/14 14:27
 * @Version 1.0
 */
public class Day32Demo6 {
    public static void main(String[] args) {
        List<String> words = List.of("lazy", "snail", "lazysnail");

        List<String> result = words.stream()
                .<String>mapMulti((word, consumer) -> {
                    consumer.accept(word.toUpperCase());
                    consumer.accept(word.toLowerCase());
                })
                .toList();

        System.out.println("结果: " + result);
    }
}

对每个单词,生成一个大写和一个小写版本。

排序

以这个作为数据源:

java 复制代码
List<String> names = List.of("lazysnail", "懒惰蜗牛", "lazysnailstudio", "lazysnailstudio.com");

sorted(): 按自然顺序排序。

java 复制代码
List<String> sortedNames = names.stream()
                .sorted()
                .toList();
        
System.out.println("按自然顺序排序: " + sortedNames);

sorted(Comparator<T> c): 按自定义比较器排序。

java 复制代码
List<String> sortedByLength = names.stream()
                .sorted(Comparator.comparingInt(String::length))
                .toList();
        
System.out.println("按长度排序: " + sortedByLength);

List<String> sortedByLengthDesc = names.stream()
                .sorted(Comparator.comparingInt(String::length).reversed())
                .toList();
                
System.out.println("按长度降序排序: " + sortedByLengthDesc);

2.3终端操作

终端操作是流水线的打包站,它会触发所有惰性操作的执行,并关闭流。

常用的终端操作:

循环:

forEach(Consumer<T> c)

java 复制代码
List<String> names = List.of("lazysnail", "懒惰蜗牛", "lazysnailstudio.com");
names.stream().forEach(System.out::println);

匹配:

anyMatch(Predicate<T> p): 是否有至少一个元素匹配。

java 复制代码
List<String> names = List.of("lazysnail", "懒惰蜗牛", "lazysnailstudio.com");
System.out.println(names.stream().anyMatch("lazysnail"::equals));

allMatch(Predicate<T> p): 是否所有元素都匹配。

java 复制代码
List<String> names = List.of("lazysnail", "懒惰蜗牛", "lazysnailstudio.com");
System.out.println(names.stream().allMatch("lazysnail"::equals));

noneMatch(Predicate<T> p): 是否没有元素匹配。

java 复制代码
List<String> names = List.of("lazysnail", "懒惰蜗牛", "lazysnailstudio.com");
System.out.println(names.stream().noneMatch("lazysnail"::equals));

查找:

findFirst(): 返回第一个元素(Optional<T>)。

java 复制代码
List<String> names = List.of("lazysnail", "懒惰蜗牛", "lazysnailstudio.com");
        Optional<String> firstMatch = names.stream()
                .filter(name -> name.contains("snail"))
                .findFirst();
        firstMatch.ifPresentOrElse(
            foundName -> System.out.println("找到了第一个匹配项: " + foundName),
            () -> System.out.println("没有找到任何匹配项。")
        );

通过findFirst方法查找第一个包含snail的字符串。

用Optional的ifPresentOrElse来处理结果,如果找到了,就执行第一个Lambda;如果没找到,就执行第二个。

findAny(): 返回任意一个元素(Optional<T>),在并行流中更高效。

java 复制代码
List<String> names = List.of("lazysnail", "懒惰蜗牛", "lazysnailstudio.com");

Optional<String> anyMatch = names.stream()
                .filter(name -> name.contains("snail"))
                .findAny();
String result = anyMatch.orElse("默认值:没找到");
        
System.out.println("查找结果: " + result);

查找任意一个包含 snail的字符串,使用Optional的orElse来处理结果。如果找到了,就返回找到的值;如果没找到,就返回一个默认值。

归约:

count(): 返回流中元素个数。

java 复制代码
long totalCount = names.stream().count();
System.out.println("列表中的元素总数是: " + totalCount);

reduce(T identity, BinaryOperator<T> accumulator): 将流中元素反复结合起来,得到一个值。

java 复制代码
Optional<String> combinedString = names.stream()
                .reduce((accumulator, element) -> accumulator + ", " + element);
        
        combinedString.ifPresent(
            result -> System.out.println("拼接后的字符串: " + result)
        );

用reduce把所有字符串拼接起来,reduce的这个重载版本不接受初始值,因此返回一个Optional,防止流为空。

collect(Collector c): 将流转换为其他形式(如List, Set, Map)。这是最重要、最强大的终端操作。

java 复制代码
Map<String, Integer> nameLengthMap = names.stream()
                .collect(Collectors.toMap(
                        name -> name,        
                        String::length       
                ));
System.out.println("转换后的Map: " + nameLengthMap);

把流转换成Map<String, Integer>。

三、Collectors

Collectors是一个工具类,预制了很多收集器,能满足绝大部分数据处理需求。

3.1收集到集合

java 复制代码
public static void main(String[] args) {
        List<String> names = List.of("lazysnail", "懒惰蜗牛", "lazysnailstudio.com");

        List<String> nameList = names.stream().toList();

        Set<String> nameSet = names.stream().collect(Collectors.toSet());

        LinkedList<String> nameLinkedList = names.stream()
                .collect(Collectors.toCollection(LinkedList::new));
    }

3.2聚合计算

java 复制代码
public static void main(String[] args) {
        List<Integer> intList = Arrays.asList(300, 420, 180, 600, 210);

        int total = intList.stream()
                .collect(Collectors.summingInt(Integer::intValue));

        double avg = intList.stream()
                .collect(Collectors.averagingInt(Integer::intValue));

        IntSummaryStatistics statistics = intList.stream()
                .collect(Collectors.summarizingInt(Integer::intValue));

        System.out.println("和: " + total);
        System.out.println("平均值: " + avg);
        System.out.println("完整统计:");
        System.out.println("记录数: " + statistics.getCount());
        System.out.println("最大: " + statistics.getMax());
        System.out.println("最小: " + statistics.getMin());
        System.out.println("总和: " + statistics.getSum());
        System.out.println("平均值: " + statistics.getAverage());
    }

3.3分组与分区

groupingBy(Function classifier): 分组,类似于SQL的GROUP BY。

partitioningBy(Predicate predicate): 分区,是groupingBy的一种特殊情况,键是布尔值。

java 复制代码
package com.lazy.snail.day32;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @ClassName Day32Demo8
 * @Description TODO
 * @Author lazysnail
 * @Date 2025/7/14 15:47
 * @Version 1.0
 */
public class Day32Demo8 {
    public static void main(String[] args) {
        List<Student> students = Arrays.asList(
                new Student("懒惰蜗牛", "数学", 85),
                new Student("二蛋", "数学", 72),
                new Student("小二狗", "物理", 92),
                new Student("二狗子", "物理", 68),
                new Student("狗蛋", "数学", 93),
                new Student("小阿三", "化学", 78)
        );

        Map<String, List<Student>> bySubject = students.stream()
                .collect(Collectors.groupingBy(Student::getSubject));

        bySubject.forEach((subject, list) -> {
            System.out.println(subject + ": " +
                    list.stream().map(Student::getName).collect(Collectors.joining(", ")));
        });

        Map<String, List<Student>> byGrade = students.stream()
                .collect(Collectors.groupingBy(student -> {
                    int score = student.getScore();
                    if (score >= 90) return "A";
                    else if (score >= 80) return "B";
                    else if (score >= 70) return "C";
                    else return "D";
                }));

        byGrade.forEach((grade, list) -> {
            System.out.println(grade + ": " +
                    list.stream().map(Student::getName).collect(Collectors.joining(", ")));
        });

        Map<Boolean, List<Student>> passOrFail = students.stream()
                .collect(Collectors.partitioningBy(student -> student.getScore() >= 60));

        System.out.println("及格学生: " +
                passOrFail.get(true).stream().map(Student::getName).collect(Collectors.joining(", ")));
        System.out.println("不及格学生: " +
                passOrFail.get(false).stream().map(Student::getName).collect(Collectors.joining(", ")));
    }
}
class Student {
    private String name;
    private String subject;
    private int score;

    public Student(String name, String subject, int score) {
        this.name = name;
        this.subject = subject;
        this.score = score;
    }

    public String getName() { return name; }
    public String getSubject() { return subject; }
    public int getScore() { return score; }
}

用groupingBy分别进行了按照学科和成绩等级(自定义)进行了分组。

用partitioningBy按照是否及格(60分)进行了分区。

案例中使用到的Collectors.joining方法就是把流中的元素拼接成一个字符串。

结语

Stream API提供了丰富的流操作,非常适合处理集合数据,让处理过程更加的简洁方便。

虽然看着内容比较多,但是多实操一下,上手非常快。

之前集合中的案例都可以尝试用流操作重构下,感受一下流操作。

下一篇预告

Day33 | Java中的Optional

如果你觉得这系列文章对你有帮助,欢迎关注专栏,我们一起坚持下去!

更多文章请关注我的公众号《懒惰蜗牛工坊》

相关推荐
我命由我123454 小时前
Python Flask 开发 - Flask 快速上手(Flask 最简单的案例、Flask 处理跨域、Flask 基础接口)
服务器·开发语言·后端·python·学习·flask·学习方法
雨中飘荡的记忆4 小时前
Canal深度解析:MySQL增量数据订阅与消费实战
java
hhzz4 小时前
Activiti7工作流(五)流程操作
java·activiti·工作流引擎·工作流
慧都小项4 小时前
JAVA自动化测试平台Parasoft Jtest 插件Eclipse/IDEA安装教程
java·软件测试·测试工具·eclipse·intellij-idea
running up5 小时前
Spring核心深度解析:AOP与事务管理(TX)全指南
java·数据库·spring
一水鉴天5 小时前
整体设计 定稿 之6 完整设计文档讨论及定稿 之1(豆包周助手)
java·前端·数据库
五阿哥永琪5 小时前
Spring Boot 权限控制三件套:JWT 登录校验 + 拦截器 + AOP 角色注解实战
java·spring boot·python
光算科技5 小时前
商品颜色/尺码选项太多|谷歌爬虫不收录怎么办
java·javascript·爬虫
派大鑫wink5 小时前
分享一些在Spring Boot中进行参数配置的最佳实践
java·spring boot·后端