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

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

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

相关推荐
Penge6661 小时前
Go 接口编译期断言
后端
我是一颗柠檬1 小时前
【MySQL全面教学】MySQL面试高频考点汇总Day15(2026年)
数据库·后端·mysql·面试
橙淮2 小时前
并发编程(六)
java·jvm
拽着尾巴的鱼儿2 小时前
springboot openfeign 自定义feign 接口重试机制
java·spring boot·后端
白露与泡影2 小时前
2026大厂Java面试题大全!牛客网最新版
java·开发语言
Ceelog2 小时前
久坐党自救指南:屏幕前 8 小时,身体到底在经历什么
前端·后端
EntyIU3 小时前
JVM内存与GC笔记
java·jvm·笔记
XS0301063 小时前
并发编程 六
java·后端
yaoxin5211233 小时前
419. 现代 Java IO 最佳实践 - 写入文本文件
java·windows·python
雪宫街道3 小时前
synchronized 锁的范围:对象锁、类锁与代码块锁
java·jvm·后端·面试