Java8新特性之Stream API

一、 Java 8 的 Stream API

Stream API 是 Java 8 引入的用于处理集合数据(如 List、Set)的新方式。
它提供了一个高效、易读、链式的方式来操作数据源(集合、数组等)。

Stream = 数据流水线
数据经过一系列处理,最后汇聚成结果,中间不会修改原集合,且支持并行处理,性能高

二、Stream 处理的四个步骤

  1. 获取 Stream:从集合、数组等数据源生成 Stream。
  2. 中间操作:对数据进行一系列处理(过滤、映射、排序等),返回新的 Stream
  3. 终止操作:真正触发计算,得到结果(forEach、collect、count等)。
  4. 特点惰性执行(中间操作不会立即执行,遇到终止操作时才执行)。

三、具体使用

1:创建 Stream

import java.util.*;
import java.util.stream.*;

public class StreamCreateDemo {
    public static void main(String[] args) {
        // 1. 从集合创建
        List<String> list = Arrays.asList("Apple", "Banana", "Cherry");
        Stream<String> stream1 = list.stream();

        // 2. 从数组创建
        String[] arr = {"Java", "Python", "C++"};
        Stream<String> stream2 = Arrays.stream(arr);

        // 3. 使用Stream.of()
        Stream<Integer> stream3 = Stream.of(1, 2, 3, 4, 5);

        // 4. 无限流(只演示,不常用)
        Stream<Double> stream4 = Stream.generate(Math::random).limit(5);
        stream4.forEach(System.out::println); // 打印5个随机数
    }
}

2:常见中间操作(filter、map、sorted)

import java.util.*;
import java.util.stream.*;

public class StreamMiddleOperationDemo {
    public static void main(String[] args) {
        List<String> fruits = Arrays.asList("Apple", "Banana", "Avocado", "Cherry", "Blueberry");

        // 示例:过滤以"A"开头的水果,转为大写并排序
        fruits.stream()
                .filter(fruit -> fruit.startsWith("A"))    // 过滤出A开头
                .map(String::toUpperCase)                  // 转为大写
                .sorted()                                  // 按字母排序
                .forEach(System.out::println);             // 遍历打印
    }
}

输出:

APPLE
AVOCADO

3:终止操作(collect、count、anyMatch)

import java.util.*;
import java.util.stream.*;

public class StreamTerminalOperationDemo {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("Dog", "Cat", "Elephant", "Tiger");

        // 1. collect:收集结果为 List
        List<String> filteredList = list.stream()
                .filter(name -> name.length() > 3)
                .collect(Collectors.toList());
        System.out.println(filteredList); // [Elephant, Tiger]

        // 2. count:统计数量
        long count = list.stream()
                .filter(name -> name.contains("i"))
                .count();
        System.out.println("包含'i'的数量: " + count); // 1

        // 3. anyMatch:是否存在满足条件的元素
        boolean hasElephant = list.stream()
                .anyMatch(name -> name.equalsIgnoreCase("elephant"));
        System.out.println("有elephant吗?" + hasElephant); // true
    }
}

4:复杂流处理

import java.util.*;
import java.util.stream.*;

public class StreamComplexExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // 需求:找到所有偶数,平方后大于10的,收集成列表
        List<Integer> result = numbers.stream()
                .filter(n -> n % 2 == 0)           // 过滤偶数
                .map(n -> n * n)                   // 平方
                .filter(n -> n > 10)               // 筛选平方后大于10
                .collect(Collectors.toList());     // 收集成List

        System.out.println(result); // [16, 36, 64, 100]
    }
}

四、常用的 Stream 操作汇总表

分类方法说明
获取流stream(), of(), generate()创建Stream
中间操作filter()过滤元素
中间操作map()元素转换
中间操作sorted()排序
中间操作distinct()去重
中间操作limit(), skip()截取或跳过元素
终止操作forEach()遍历元素
终止操作collect()收集到集合
终止操作count()统计数量
终止操作anyMatch(), allMatch(), noneMatch()匹配判断
终止操作findFirst(), findAny()查找元素

五、Stream 流程图(简版)

数据源 → 中间操作1 → 中间操作2 → ... → 终止操作(结果)
List → filter → map → collect

六、Stream 高阶操作

1. groupingBy() — 分组操作

把元素按照某个规则进行分类成 Mapkey 是分组依据,value 是元素集合。

  • 按字符串长度分组
import java.util.*;
import java.util.stream.*;

public class StreamGroupingByDemo {
    public static void main(String[] args) {
        List<String> words = Arrays.asList("Apple", "Banana", "Cat", "Dog", "Elephant");

        // 按字符串长度分组
        Map<Integer, List<String>> grouped = words.stream()
                .collect(Collectors.groupingBy(String::length));

        // 打印分组结果
        grouped.forEach((length, wordList) -> 
            System.out.println(length + "个字符: " + wordList)
        );
    }
}

输出:

5个字符: [Apple]
6个字符: [Banana]
3个字符: [Cat, Dog]
8个字符: [Elephant]

2. partitioningBy() — 分区操作(true/false两组)

按照某个布尔条件,把集合一分为二(符合/不符合)。

  • 分成偶数和奇数
import java.util.*;
import java.util.stream.*;

public class StreamPartitioningByDemo {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);

        // 分区:是否为偶数
        Map<Boolean, List<Integer>> partitioned = numbers.stream()
                .collect(Collectors.partitioningBy(n -> n % 2 == 0));

        System.out.println("偶数: " + partitioned.get(true));
        System.out.println("奇数: " + partitioned.get(false));
    }
}

输出:

偶数: [2, 4, 6]
奇数: [1, 3, 5]

3. reduce() — 归约操作(聚合)

把流中的元素反复结合起来,最终得到一个值,类似数学中的累加/累乘

  • 求所有数字的和
import java.util.*;
import java.util.stream.*;

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

        // reduce 第一个参数是初始值0,第二个参数是累加逻辑
        int sum = nums.stream()
                .reduce(0, (a, b) -> a + b);

        System.out.println("总和: " + sum); // 15
    }
}

reduce的核心就是不断地把两个元素合成一个,直到只剩一个!

4. flatMap() — 拍平多个集合

把每个元素映射成流,然后展开成一个统一流

flatMap = 映射 + 扁平化处理

  • 把多个List合并成一个大List
import java.util.*;
import java.util.stream.*;

public class StreamFlatMapDemo {
    public static void main(String[] args) {
        List<List<String>> nestedList = Arrays.asList(
                Arrays.asList("A", "B"),
                Arrays.asList("C", "D"),
                Arrays.asList("E")
        );

        // 使用flatMap展开
        List<String> flatList = nestedList.stream()
                .flatMap(Collection::stream)
                .collect(Collectors.toList());

        System.out.println(flatList); // [A, B, C, D, E]
    }
}

5、Stream 高阶用法对照总结表

方法作用示例
groupingBy()按规则分组按长度分组字符串
partitioningBy()按true/false分区分偶数奇数
reduce()元素归约求和、求最大值
flatMap()扁平化流合并嵌套集合

6、综合流操作

来一个更实际、综合的例子(开发中常遇到):

给定一组员工数据,筛选出工资大于6000的,按部门分组,并列出每个部门的员工姓名列表。

import java.util.*;
import java.util.stream.*;

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

    // 构造器 & getter方法
    public Employee(String name, String department, double salary) {
        this.name = name;
        this.department = department;
        this.salary = salary;
    }
    public String getName() { return name; }
    public String getDepartment() { return department; }
    public double getSalary() { return salary; }
}

public class StreamEmployeeExample {
    public static void main(String[] args) {
        List<Employee> employees = Arrays.asList(
            new Employee("Alice", "IT", 7000),
            new Employee("Bob", "HR", 5000),
            new Employee("Charlie", "IT", 8000),
            new Employee("David", "Finance", 9000),
            new Employee("Eve", "HR", 6500)
        );

        // 筛选工资>6000, 按部门分组,列出名字
        Map<String, List<String>> result = employees.stream()
                .filter(emp -> emp.getSalary() > 6000) // 筛选工资高的
                .collect(Collectors.groupingBy(
                    Employee::getDepartment,               // 按部门分组
                    Collectors.mapping(Employee::getName, Collectors.toList()) // 只保留名字
                ));

        // 打印结果
        result.forEach((dept, names) -> 
            System.out.println(dept + "部门: " + names)
        );
    }
}

输出:

IT部门: [Alice, Charlie]
Finance部门: [David]
HR部门: [Eve]

7、理解

关键词一句话解释
filter过滤留下
map映射改变
flatMap打碎扁平
reduce累加归一
collect收集保存
groupingBy分类分组
partitioningBy分成两堆

8、大数据列表的分页处理(模拟分页查询)

  • 有一个超大List(比如10万条数据)
  • 前端需要分页展示(比如每页20条)
  • 需要用Stream优雅、高效地分页处理
    解决思路
    使用 skip(offset).limit(size) 实现分页。
    skip(n):跳过前n个元素
    limit(m):取接下来的m个元素
import java.util.*;
import java.util.stream.*;

public class StreamPagingExample {
    public static void main(String[] args) {
        // 模拟一个超大List(10万条)
        List<Integer> bigList = IntStream.rangeClosed(1, 100_000)
                                         .boxed()
                                         .collect(Collectors.toList());

        int pageSize = 20; // 每页大小
        int pageNumber = 3; // 第3页(注意:第一页是pageNumber=1)

        List<Integer> pageData = bigList.stream()
                .skip((pageNumber - 1) * pageSize) // 跳过前2页
                .limit(pageSize)                  // 取第3页数据
                .collect(Collectors.toList());

        System.out.println("第 " + pageNumber + " 页数据:" + pageData);
    }
}
第 3 页数据:[41, 42, 43, ..., 60]

性能提示

  • 如果集合特别大,考虑用并行流parallelStream())提升速度
  • 小集合(几千条以内)一般用普通 stream()就够了,避免并行开销
  • 分页参数(skip、limit)写在链式操作越靠前越好,减少后续流的元素数量

9、复杂对象的多级分组与统计

  • 有一批员工数据
  • 要求:
    • 按部门分组
    • 每个部门再按职位分组
    • 每组统计人数
import java.util.*;
import java.util.stream.*;

class Employee {
    private String name;
    private String department;
    private String position;

    public Employee(String name, String department, String position) {
        this.name = name;
        this.department = department;
        this.position = position;
    }
    public String getDepartment() { return department; }
    public String getPosition() { return position; }
}

public class StreamMultiLevelGrouping {
    public static void main(String[] args) {
        List<Employee> employees = Arrays.asList(
            new Employee("Alice", "IT", "Developer"),
            new Employee("Bob", "IT", "Developer"),
            new Employee("Charlie", "IT", "Manager"),
            new Employee("David", "HR", "Manager"),
            new Employee("Eve", "HR", "Recruiter"),
            new Employee("Frank", "Finance", "Analyst")
        );

        // 多级分组+人数统计
        Map<String, Map<String, Long>> grouped = employees.stream()
            .collect(Collectors.groupingBy(
                Employee::getDepartment,
                Collectors.groupingBy(
                    Employee::getPosition,
                    Collectors.counting()
                )
            ));

        // 打印结果
        grouped.forEach((dept, posMap) -> {
            System.out.println("部门:" + dept);
            posMap.forEach((pos, count) ->
                System.out.println("  职位:" + pos + ",人数:" + count)
            );
        });
    }
}
部门:IT
  职位:Developer,人数:2
  职位:Manager,人数:1
部门:HR
  职位:Manager,人数:1
  职位:Recruiter,人数:1
部门:Finance
  职位:Analyst,人数:1

10、List去重(基于对象属性)

  • 有个对象集合(List<User>)
  • 需要根据某个字段(比如email)去重!
import java.util.*;
import java.util.function.Function;
import java.util.stream.*;

class User {
    private String name;
    private String email;

    public User(String name, String email) {
        this.name = name;
        this.email = email;
    }
    public String getEmail() { return email; }
    @Override
    public String toString() { return name + " <" + email + ">"; }
}

public class StreamDistinctByKey {
    public static void main(String[] args) {
        List<User> users = Arrays.asList(
            new User("Alice", "alice@example.com"),
            new User("Bob", "bob@example.com"),
            new User("Alice Duplicate", "alice@example.com") // email重复
        );

        List<User> distinctUsers = users.stream()
            .filter(distinctByKey(User::getEmail)) // 根据email去重
            .collect(Collectors.toList());

        distinctUsers.forEach(System.out::println);
    }

    // 核心辅助方法:根据Key去重
    public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
        Set<Object> seen = ConcurrentHashMap.newKeySet();
        return t -> seen.add(keyExtractor.apply(t));
    }
}
Alice <alice@example.com>
Bob <bob@example.com>

性能提示

  • ConcurrentHashMap.newKeySet() 保证线程安全,即使在parallelStream()中也能用!
  • 如果数据量小,可以直接用普通HashSet

11、Stream 进阶总结

技巧核心方法场景示例
分页处理skip() + limit()大List分页
多级分组groupingBy() -> groupingBy()部门+职位统计
对象属性去重filter(distinctByKey())按email去重
动态条件过滤filter(predicate组合)灵活复杂查询
流性能提升parallelStream()大数据量并行处理

六、Stream 性能优化实战

1、 及时终止中间操作(short-circuiting)

  • 流式处理默认是惰性求值(只有终止操作才真正执行)
  • 短路操作(比如limit(), findFirst(), anyMatch())可以让流尽早结束
// 查找第一个符合条件的人
Optional<String> firstMatch = Stream.of("Tom", "Jerry", "Spike")
    .filter(name -> name.startsWith("J"))
    .findFirst(); // findFirst短路:找到第一个就停止后续filter

小流量数据,尽量使用 findFirst(), anyMatch(),可以避免不必要的计算。

2、 使用 parallelStream()

  • parallelStream() 利用多核CPU并行处理
  • 线程切换开销数据分片成本可能适得其反!
List<Integer> numbers = IntStream.rangeClosed(1, 1_000_000).boxed().collect(Collectors.toList());

long start = System.currentTimeMillis();
numbers.parallelStream() // 并行流
       .reduce(0, Integer::sum);
long end = System.currentTimeMillis();

System.out.println("并行计算耗时:" + (end - start) + "ms");

大数据量(10万以上)、CPU密集型任务,适合并行流
小数据量或IO密集型操作,不建议用parallelStream()

3、 避免流中嵌套流(Stream里面forEach)

  • 流里再套流,容易失去流的优势,还增加时间复杂度!
list.stream().forEach(a -> anotherList.stream().forEach(b -> doSomething(a, b)));

正确写法:考虑flatMap()平铺一层!

4、 优先使用原始类型流 IntStream / LongStream

  • Stream<Integer> 会频繁装箱/拆箱(Integer ↔ int)
  • IntStream 直接操作基本类型,更快!
// 不推荐
Stream<Integer> integerStream = Stream.of(1, 2, 3);

// 推荐
IntStream intStream = IntStream.of(1, 2, 3);

大量数学计算时,IntStream/LongStream效果明显!

5 减少中间集合,直接链式操作

  • 每次 .collect() 会生成中间集合,增加内存压力
  • 可以流式链式操作到底,最后一次性 collect

推荐链式写法

List<String> result = people.stream()
    .filter(p -> p.getAge() > 18)
    .map(Person::getName)
    .sorted()
    .collect(Collectors.toList());

八、面试 Stream 高阶问题总结

1:Stream 和普通 for 循环有什么区别?

  • Stream关注声明式处理(what)而非命令式处理(how)
  • 支持链式调用懒惰求值并行加速
  • 更易读、更适合大数据量处理,但在小规模场景下性能差异不大

2:parallelStream 真的是越快越好吗?

  • 不一定!小数据量时,由于线程调度开销,parallelStream反而慢
  • CPU密集型、大数据量任务适合;IO密集型、不规则任务慎用

3:Stream操作是线程安全的吗?

  • 流本身是不安全
  • parallelStream()更要注意共享变量的竞争问题
  • 如果要保证安全,需要用同步集合或使用无状态操作

4:什么是短路操作?有哪些常见短路方法?

  • 短路操作是指在满足一定条件时提前终止流处理,提高效率
  • 常见短路方法有:
    • findFirst()
    • findAny()
    • anyMatch()
    • allMatch()
    • noneMatch()
    • limit(n)

5:Stream 能重复使用吗?

  • 不能!
  • 流一旦消费或关闭(比如执行终止操作collect()),就不能再操作了
  • 需要重新生成新的流!

6:进阶总结版 Stream

分类技能点常用方法
创建流list、数组、生成器stream(), of(), iterate(), generate()
中间操作过滤、映射、排序filter(), map(), flatMap(), sorted(), distinct()
终止操作收集、聚合、遍历collect(), reduce(), forEach(), count()
并行流多核加速处理parallelStream()
分组/分区高级收集器groupingBy(), partitioningBy()
性能优化懒执行、短路、基本类型流skip() + limit(), IntStream

九、Stream 常见错误大全 + 正确写法总结

1:流被消费后再次使用

  • 错误:
Stream<String> stream = Stream.of("a", "b", "c");
stream.forEach(System.out::println);
stream.forEach(System.out::println); // 报错:stream has already been operated upon or closed
  • 正确:
Stream<String> stream1 = Stream.of("a", "b", "c");
stream1.forEach(System.out::println);

Stream<String> stream2 = Stream.of("a", "b", "c");
stream2.forEach(System.out::println); // 新建新的流

流只能使用一次,要多次使用,重新生成!

2:在并行流中操作非线程安全集合

  • 错误:
List<Integer> list = new ArrayList<>();
IntStream.range(0, 1000).parallel().forEach(list::add); // 线程不安全,数据丢失
  • 正确:
List<Integer> list = Collections.synchronizedList(new ArrayList<>());
IntStream.range(0, 1000).parallel().forEach(list::add);

并行流操作共享资源,要用线程安全集合!

3:流中forEach里嵌套Stream操作

  • 错误:
list.stream().forEach(a -> anotherList.stream().forEach(b -> doSomething(a, b)));
  • 正确:
list.stream()
    .flatMap(a -> anotherList.stream().map(b -> combine(a, b)))
    .forEach(System.out::println);

避免 forEach 套 Stream,应该用 flatMap 平铺处理!

4:滥用 parallelStream 导致性能更差

  • 错误:
List<String> smallList = Arrays.asList("A", "B", "C");
smallList.parallelStream().forEach(System.out::println); // 小数据量,不适合并行!

小数据量不要用 parallelStream()

十、Stream 模板

标准流式处理骨架(可直接套用)

List<User> users = fetchUsersFromDB();

// 流式处理模板
List<String> adultUserNames = Optional.ofNullable(users)
    .orElse(Collections.emptyList())
    .stream()
    .filter(user -> Objects.nonNull(user.getAge()) && user.getAge() >= 18) // 过滤合法且年满18岁
    .sorted(Comparator.comparing(User::getName)) // 按名字排序
    .map(User::getName) // 提取名字
    .distinct() // 去重
    .collect(Collectors.toList()); // 收集到List
  • 流程分解:
步骤说明
1. 安全处理空集合Optional.ofNullable().orElse(Collections.emptyList())
2. 过滤数据filter()
3. 排序sorted()
4. 转换映射map()
5. 去重distinct()
6. 收集collect()
  • 建议
项目实践推荐做法
空集合保护Optional.ofNullable(list).orElse(Collections.emptyList())
Null保护filter()中做Objects.nonNull()校验
小批量处理不要用parallelStream()
复杂映射flatMap()展开
日志排查尽量在流前后打印日志,方便排查异常
  • 总结
场景示例
过滤filter(x -> x > 0)
排序sorted(Comparator.comparing(x -> x.getAge()))
映射map(x -> x.getName())
平铺flatMap(list -> list.stream())
终止collect(Collectors.toList())
分组Collectors.groupingBy(x -> x.getType())
聚合reduce(0, Integer::sum)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值