【Java从入门到入土】28:Stream API:告别for循环的新时代

【Java从入门到入土】28:Stream API:告别for循环的新时代

Stream API是Java 8的核心特性之一,它彻底改变了集合数据的处理方式------从"命令式"的for循环遍历,转向"声明式"的流式操作。新手常问:"Stream和for循环有啥区别?为什么说它更优雅?并行流真的更快吗?" 今天从"为什么用Stream""怎么创建流""核心操作链"三个维度,把Stream API的设计逻辑和实战用法讲透,让你彻底告别繁琐的for循环,用更简洁的代码实现数据处理。

🤔 为什么需要Stream:声明式编程的优势

在Stream出现前,处理集合数据依赖命令式编程 (for循环):手动控制遍历、判断、转换的每一步,代码冗长且易出错;而Stream采用声明式编程:只需要描述"要做什么",无需关心"怎么做"(遍历、迭代由JDK自动优化)。

1. 命令式 vs 声明式:代码对比

需求:从员工列表中筛选出薪资>10000的研发人员,提取姓名并按薪资降序排列。

(1)for循环(命令式)
java 复制代码
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

class Employee {
    private String name;
    private String department;
    private int salary;

    public Employee(String name, String department, int salary) {
        this.name = name;
        this.department = department;
        this.salary = salary;
    }

    // getter/setter省略
    public String getName() { return name; }
    public String getDepartment() { return department; }
    public int getSalary() { return salary; }
}

public class ForLoopDemo {
    public static void main(String[] args) {
        List<Employee> employees = initEmployees();
        List<Employee> devList = new ArrayList<>();

        // 1. 筛选研发部+薪资>10000
        for (Employee e : employees) {
            if ("研发部".equals(e.getDepartment()) && e.getSalary() > 10000) {
                devList.add(e);
            }
        }

        // 2. 按薪资降序排序
        Collections.sort(devList, new Comparator<Employee>() {
            @Override
            public int compare(Employee o1, Employee o2) {
                return o2.getSalary() - o1.getSalary();
            }
        });

        // 3. 提取姓名
        List<String> nameList = new ArrayList<>();
        for (Employee e : devList) {
            nameList.add(e.getName());
        }

        System.out.println(nameList);
    }

    private static List<Employee> initEmployees() {
        List<Employee> list = new ArrayList<>();
        list.add(new Employee("张三", "研发部", 15000));
        list.add(new Employee("李四", "市场部", 8000));
        list.add(new Employee("王五", "研发部", 12000));
        list.add(new Employee("赵六", "研发部", 9000));
        return list;
    }
}
(2)Stream API(声明式)
java 复制代码
import java.util.List;
import java.util.stream.Collectors;

public class StreamDemo {
    public static void main(String[] args) {
        List<Employee> employees = initEmployees();

        // 一行代码完成:筛选→排序→提取姓名
        List<String> nameList = employees.stream()
                .filter(e -> "研发部".equals(e.getDepartment()) && e.getSalary() > 10000) // 筛选
                .sorted((o1, o2) -> o2.getSalary() - o1.getSalary()) // 排序
                .map(Employee::getName) // 提取姓名
                .collect(Collectors.toList()); // 收集结果

        System.out.println(nameList); // [张三, 王五]
    }

    private static List<Employee> initEmployees() {
        // 同上面的初始化逻辑
        List<Employee> list = new ArrayList<>();
        list.add(new Employee("张三", "研发部", 15000));
        list.add(new Employee("李四", "市场部", 8000));
        list.add(new Employee("王五", "研发部", 12000));
        list.add(new Employee("赵六", "研发部", 9000));
        return list;
    }
}

2. Stream核心优势

特性 命令式(for循环) 声明式(Stream)
代码风格 冗长、嵌套多、可读性差 简洁、链式调用、语义清晰
关注点 怎么遍历(how) 要做什么(what)
并行处理 手动实现线程/锁,易出错 一行代码切换并行流(parallelStream()
代码复用 难以复用(遍历逻辑耦合) 可封装为函数,复用性高
惰性求值 无(立即执行) 有(中间操作不执行,终端操作触发)

🚀 流的创建:集合、数组、值、函数生成

Stream的核心是"流"(数据序列),创建流是使用Stream API的第一步,JDK提供了多种创建方式,覆盖绝大多数场景。

1. 从集合创建(最常用)

所有实现Collection接口的集合(List、Set、Queue)都可以通过stream()创建串行流,parallelStream()创建并行流:

java 复制代码
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class StreamCreateFromCollection {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("Java");
        list.add("Python");
        list.add("Go");

        // 1. 串行流
        Stream<String> stream = list.stream();
        // 2. 并行流(多线程处理)
        Stream<String> parallelStream = list.parallelStream();
    }
}

2. 从数组创建

通过Arrays.stream()创建流,支持基本类型数组(int[]、long[])和对象数组:

java 复制代码
import java.util.Arrays;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class StreamCreateFromArray {
    public static void main(String[] args) {
        // 1. 对象数组
        String[] arr = {"a", "b", "c"};
        Stream<String> arrStream = Arrays.stream(arr);

        // 2. 基本类型数组(IntStream/LongStream/DoubleStream)
        int[] numArr = {1, 2, 3};
        IntStream intStream = Arrays.stream(numArr);
    }
}

3. 从单个/多个值创建

通过Stream.of()直接创建流,支持任意数量的参数:

java 复制代码
import java.util.stream.Stream;

public class StreamCreateFromValues {
    public static void main(String[] args) {
        // 单个值
        Stream<String> singleValueStream = Stream.of("Hello");
        // 多个值
        Stream<Integer> multiValueStream = Stream.of(1, 2, 3, 4);
    }
}

4. 函数生成(无限流)

通过Stream.generate()(生成无限常量流)或Stream.iterate()(生成无限迭代流)创建,需配合limit()限制长度:

java 复制代码
import java.util.stream.Stream;

public class StreamCreateFromFunction {
    public static void main(String[] args) {
        // 1. generate:生成10个随机数(Supplier接口)
        Stream<Double> randomStream = Stream.generate(Math::random)
                .limit(10); // 限制为10个元素,否则无限生成
        randomStream.forEach(System.out::println);

        // 2. iterate:生成1-10的整数(初始值+迭代规则)
        Stream<Integer> iterateStream = Stream.iterate(1, n -> n + 1)
                .limit(10);
        iterateStream.forEach(System.out::println); // 1,2,...,10
    }
}

5. 其他创建方式

  • 空流:Stream.empty()(避免返回null流);
  • 文件流:Files.lines(Path)(按行读取文件内容);
  • 字符串流:String.chars()(将字符串转为字符流)。

🔧 中间操作:filter、map、flatMap、sorted、distinct

Stream的操作分为"中间操作"和"终端操作":

  • 中间操作:返回新的Stream,是"惰性的"(不立即执行,仅记录操作);
  • 终端操作:触发流的执行,返回非Stream结果(如List、Integer、void)。

中间操作是数据处理的核心,常用的有筛选、映射、排序、去重等。

1. filter:筛选元素

根据条件过滤元素,保留Predicate返回true的元素:

java 复制代码
import java.util.List;
import java.util.stream.Collectors;

public class StreamFilter {
    public static void main(String[] args) {
        List<Integer> nums = List.of(1, 2, 3, 4, 5, 6);
        // 筛选偶数
        List<Integer> evenNums = nums.stream()
                .filter(n -> n % 2 == 0)
                .collect(Collectors.toList());
        System.out.println(evenNums); // [2,4,6]
    }
}

2. map:元素转换

将流中的元素映射为另一种类型(一对一转换),常用Function接口:

java 复制代码
import java.util.List;
import java.util.stream.Collectors;

public class StreamMap {
    public static void main(String[] args) {
        List<String> strList = List.of("java", "python", "go");
        // 转换为大写
        List<String> upperList = strList.stream()
                .map(String::toUpperCase)
                .collect(Collectors.toList());
        System.out.println(upperList); // [JAVA, PYTHON, GO]

        // 数字平方
        List<Integer> nums = List.of(1,2,3);
        List<Integer> squareNums = nums.stream()
                .map(n -> n * n)
                .collect(Collectors.toList());
        System.out.println(squareNums); // [1,4,9]
    }
}

3. flatMap:扁平化流

将"流中的流"扁平化为单个流(一对多转换),解决嵌套集合的遍历问题:

java 复制代码
import java.util.List;
import java.util.stream.Collectors;

public class StreamFlatMap {
    public static void main(String[] args) {
        // 嵌套集合:[[1,2], [3,4], [5,6]]
        List<List<Integer>> nestedList = List.of(
                List.of(1, 2),
                List.of(3, 4),
                List.of(5, 6)
        );

        // flatMap:将每个子列表转为流,再合并为一个流
        List<Integer> flatList = nestedList.stream()
                .flatMap(List::stream) // 子列表→流,扁平化
                .collect(Collectors.toList());

        System.out.println(flatList); // [1,2,3,4,5,6]
    }
}

4. sorted:排序

分为"自然排序"(元素实现Comparable)和"自定义排序"(传入Comparator):

java 复制代码
import java.util.List;
import java.util.stream.Collectors;

public class StreamSorted {
    public static void main(String[] args) {
        // 1. 自然排序(数字升序)
        List<Integer> nums = List.of(3, 1, 4, 2);
        List<Integer> sortedNums = nums.stream()
                .sorted()
                .collect(Collectors.toList());
        System.out.println(sortedNums); // [1,2,3,4]

        // 2. 自定义排序(数字降序)
        List<Integer> reverseNums = nums.stream()
                .sorted((a, b) -> b - a)
                .collect(Collectors.toList());
        System.out.println(reverseNums); // [4,3,2,1]

        // 3. 自定义对象排序(按员工薪资降序)
        List<Employee> employees = initEmployees();
        List<Employee> sortedEmployees = employees.stream()
                .sorted((e1, e2) -> e2.getSalary() - e1.getSalary())
                .collect(Collectors.toList());
    }

    private static List<Employee> initEmployees() {
        List<Employee> list = new ArrayList<>();
        list.add(new Employee("张三", "研发部", 15000));
        list.add(new Employee("李四", "市场部", 8000));
        list.add(new Employee("王五", "研发部", 12000));
        return list;
    }
}

5. distinct:去重

基于equals()方法去除重复元素,基本类型/自定义对象都支持(自定义对象需重写equals()hashCode()):

java 复制代码
import java.util.List;
import java.util.stream.Collectors;

public class StreamDistinct {
    public static void main(String[] args) {
        // 基本类型去重
        List<Integer> nums = List.of(1, 2, 2, 3, 3, 3);
        List<Integer> distinctNums = nums.stream()
                .distinct()
                .collect(Collectors.toList());
        System.out.println(distinctNums); // [1,2,3]

        // 自定义对象去重(需重写equals/hashCode)
        List<Employee> employees = List.of(
                new Employee("张三", "研发部", 15000),
                new Employee("张三", "研发部", 15000), // 重复元素
                new Employee("李四", "市场部", 8000)
        );
        List<Employee> distinctEmployees = employees.stream()
                .distinct()
                .collect(Collectors.toList());
        System.out.println(distinctEmployees.size()); // 2
    }
}

// 重写Employee的equals和hashCode
class Employee {
    private String name;
    private String department;
    private int salary;

    // 构造方法省略
    public Employee(String name, String department, int salary) {
        this.name = name;
        this.department = department;
        this.salary = salary;
    }

    // getter/setter省略

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Employee employee = (Employee) o;
        return salary == employee.salary &&
                Objects.equals(name, employee.name) &&
                Objects.equals(department, employee.department);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, department, salary);
    }
}

🎯 终端操作:collect、forEach、reduce、count

终端操作是Stream的"执行开关"------只有调用终端操作,中间操作才会执行,终端操作返回非Stream结果,且流只能被消费一次(消费后流关闭)。

1. collect:收集结果(最常用)

将流的结果收集为集合、数组、字符串等,核心依赖Collectors工具类,覆盖绝大多数收集场景:

java 复制代码
import java.util.List;
import java.util.Set;
import java.util.Map;
import java.util.stream.Collectors;

public class StreamCollect {
    public static void main(String[] args) {
        List<Employee> employees = initEmployees();

        // 1. 收集为List
        List<String> nameList = employees.stream()
                .map(Employee::getName)
                .collect(Collectors.toList());

        // 2. 收集为Set(自动去重)
        Set<String> deptSet = employees.stream()
                .map(Employee::getDepartment)
                .collect(Collectors.toSet());

        // 3. 收集为Map(key=姓名,value=薪资,注意key不能重复)
        Map<String, Integer> nameSalaryMap = employees.stream()
                .collect(Collectors.toMap(
                        Employee::getName, 
                        Employee::getSalary,
                        (oldValue, newValue) -> oldValue // 重复key时保留旧值
                ));

        // 4. 拼接字符串(分隔符、前缀、后缀)
        String nameStr = employees.stream()
                .map(Employee::getName)
                .collect(Collectors.joining(", ", "员工列表:[", "]"));
        System.out.println(nameStr); // 员工列表:[张三, 李四, 王五]
    }

    private static List<Employee> initEmployees() {
        List<Employee> list = new ArrayList<>();
        list.add(new Employee("张三", "研发部", 15000));
        list.add(new Employee("李四", "市场部", 8000));
        list.add(new Employee("王五", "研发部", 12000));
        return list;
    }
}

2. forEach:遍历消费

遍历流中的每个元素并执行操作(如打印、修改属性),无返回值:

java 复制代码
import java.util.List;

public class StreamForEach {
    public static void main(String[] args) {
        List<String> list = List.of("Java", "Python", "Go");

        // 1. 普通遍历
        list.stream().forEach(System.out::println);

        // 2. 并行流遍历(注意线程安全)
        list.parallelStream().forEach(s -> System.out.println(Thread.currentThread().getName() + ": " + s));
    }
}

3. reduce:归约(聚合)

将流中的元素聚合为单个值(如求和、求最大值、拼接字符串),核心是"累加器"逻辑:

java 复制代码
import java.util.List;
import java.util.Optional;

public class StreamReduce {
    public static void main(String[] args) {
        List<Integer> nums = List.of(1, 2, 3, 4);

        // 1. 求和(有初始值,返回Integer)
        Integer sum = nums.stream()
                .reduce(0, (a, b) -> a + b); // 初始值0,累加器:a=累计值,b=当前元素
        System.out.println(sum); // 10

        // 2. 求最大值(无初始值,返回Optional<Integer>,避免空指针)
        Optional<Integer> max = nums.stream()
                .reduce(Integer::max);
        max.ifPresent(System.out::println); // 4

        // 3. 拼接字符串
        List<String> strList = List.of("J", "a", "v", "a");
        String str = strList.stream()
                .reduce("", String::concat);
        System.out.println(str); // Java
    }
}

4. count:统计数量

返回流中元素的个数,返回值为long类型:

java 复制代码
import java.util.List;

public class StreamCount {
    public static void main(String[] args) {
        List<Employee> employees = initEmployees();

        // 统计研发部员工数量
        long devCount = employees.stream()
                .filter(e -> "研发部".equals(e.getDepartment()))
                .count();
        System.out.println("研发部员工数:" + devCount); // 2
    }

    private static List<Employee> initEmployees() {
        List<Employee> list = new ArrayList<>();
        list.add(new Employee("张三", "研发部", 15000));
        list.add(new Employee("李四", "市场部", 8000));
        list.add(new Employee("王五", "研发部", 12000));
        return list;
    }
}

⚡ 短路操作:findFirst、anyMatch、allMatch

短路操作是终端操作的特殊类型------只要满足条件就立即停止遍历,避免全量处理,提升性能,适用于"查找符合条件的第一个元素""判断是否存在符合条件的元素"等场景。

1. findFirst:查找第一个元素

返回流中第一个元素的Optional(避免空指针),找到后立即停止遍历:

java 复制代码
import java.util.List;
import java.util.Optional;

public class StreamFindFirst {
    public static void main(String[] args) {
        List<Employee> employees = initEmployees();

        // 查找第一个研发部员工
        Optional<Employee> firstDev = employees.stream()
                .filter(e -> "研发部".equals(e.getDepartment()))
                .findFirst();

        // 安全取值(避免NullPointerException)
        firstDev.ifPresent(e -> System.out.println("第一个研发部员工:" + e.getName())); // 张三
    }

    private static List<Employee> initEmployees() {
        List<Employee> list = new ArrayList<>();
        list.add(new Employee("张三", "研发部", 15000));
        list.add(new Employee("李四", "市场部", 8000));
        list.add(new Employee("王五", "研发部", 12000));
        return list;
    }
}

2. anyMatch:判断是否存在符合条件的元素

只要有一个元素满足条件,立即返回true,否则返回false

java 复制代码
import java.util.List;

public class StreamAnyMatch {
    public static void main(String[] args) {
        List<Employee> employees = initEmployees();

        // 判断是否有薪资>12000的员工
        boolean hasHighSalary = employees.stream()
                .anyMatch(e -> e.getSalary() > 12000);
        System.out.println(hasHighSalary); // true

        // 判断是否有财务部员工
        boolean hasFinance = employees.stream()
                .anyMatch(e -> "财务部".equals(e.getDepartment()));
        System.out.println(hasFinance); // false
    }

    private static List<Employee> initEmployees() {
        List<Employee> list = new ArrayList<>();
        list.add(new Employee("张三", "研发部", 15000));
        list.add(new Employee("李四", "市场部", 8000));
        list.add(new Employee("王五", "研发部", 12000));
        return list;
    }
}

3. allMatch:判断是否所有元素都符合条件

所有元素满足条件返回true,只要有一个不满足就立即返回false

java 复制代码
import java.util.List;

public class StreamAllMatch {
    public static void main(String[] args) {
        List<Employee> employees = initEmployees();

        // 判断是否所有员工薪资>5000
        boolean allHighSalary = employees.stream()
                .allMatch(e -> e.getSalary() > 5000);
        System.out.println(allHighSalary); // true

        // 判断是否所有员工都是研发部
        boolean allDev = employees.stream()
                .allMatch(e -> "研发部".equals(e.getDepartment()));
        System.out.println(allDev); // false
    }

    private static List<Employee> initEmployees() {
        List<Employee> list = new ArrayList<>();
        list.add(new Employee("张三", "研发部", 15000));
        list.add(new Employee("李四", "市场部", 8000));
        list.add(new Employee("王五", "研发部", 12000));
        return list;
    }
}

📌 核心总结

Stream API的核心是"声明式数据处理",告别繁琐的for循环,核心要点如下:

  1. 设计思想:声明式编程(关注what)替代命令式编程(关注how),代码更简洁、易维护;
  2. 流的生命周期:创建流→中间操作(惰性)→终端操作(触发执行),流只能消费一次;
  3. 核心操作
    • 中间操作:filter(筛选)、map(转换)、flatMap(扁平化)、sorted(排序)、distinct(去重);
    • 终端操作:collect(收集)、forEach(遍历)、reduce(归约)、count(统计);
    • 短路操作:findFirst(找第一个)、anyMatch(存在即返回)、allMatch(全满足才返回);
  4. 性能优化:短路操作减少遍历次数,并行流(parallelStream)利用多核,但需注意线程安全;
  5. 最佳实践
    • 避免在中间操作中修改外部变量(副作用);
    • 自定义对象去重/排序需重写equals/hashCode或自定义Comparator;
    • 优先使用Optional处理null,避免空指针。

掌握Stream API,你可以用更少的代码实现复杂的数据处理逻辑,无论是集合筛选、转换、聚合,还是并行处理,都能游刃有余------真正进入"告别for循环"的编程新时代。

相关推荐
qq_435287921 小时前
第9章 夸父逐日与后羿射日:死循环与进程终止?十个太阳同时值班的并行冲突
java·开发语言·git·死循环·进程终止·并行冲突·夸父逐日
小江的记录本1 小时前
【Kafka核心】架构模型:Producer、Broker、Consumer、Consumer Group、Topic、Partition、Replica
java·数据库·分布式·后端·搜索引擎·架构·kafka
止语Lab1 小时前
从手动到框架:Go DI 演进的三个拐点
开发语言·后端·golang
yaoxin5211232 小时前
397. Java 文件操作基础 - 创建常规文件与临时文件
java·开发语言·python
小短腿的代码世界2 小时前
Qt日志系统深度解析:从qDebug到企业级日志框架
开发语言·qt
dFObBIMmai2 小时前
MySQL主从同步中大事务导致的延迟_如何拆分大事务优化同步
jvm·数据库·python
szccyw02 小时前
mysql如何限制特定存储过程执行权限_MySQL存储过程安全访问
jvm·数据库·python
小白学大数据2 小时前
Python 自动化爬取网易云音乐歌手歌词实战教程
爬虫·python·okhttp·自动化
REDcker2 小时前
浏览器端Web程序性能分析与优化实战 DevTools指标与工程清单
开发语言·前端·javascript·vue·ecmascript·php·js