Skip to content

Lambda 与 Stream

一、为什么需要 Lambda 表达式?

在 Java 8 之前,传递"行为"需要写大量样板代码——定义一个匿名内部类,即使只为了传递一行逻辑。Lambda 表达式让 Java 真正支持了函数式编程风格,核心价值在于:

  • 减少样板代码:一行 Lambda 替代 5+ 行匿名内部类
  • 行为参数化:将方法逻辑作为参数传递,代码更灵活、可复用
  • 可读性提升:意图更清晰,代码即文档
  • 并行处理的基础:Stream API 依赖 Lambda,使得并行编程变得简单

对比示例:匿名内部类 vs Lambda

java
// JDK 7 及之前:匿名内部类 —— 5 行代码只为传一个比较逻辑
Collections.sort(list, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.compareTo(b);
    }
});

// JDK 8 Lambda:一行搞定
Collections.sort(list, (a, b) -> a.compareTo(b));

// 更进一步:方法引用
list.sort(String::compareTo);
java
// 线程创建对比
// 旧方式:6 行
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello");
    }
}).start();

// Lambda:1 行
new Thread(() -> System.out.println("Hello")).start();

二、Lambda 语法

2.1 基本语法

(参数列表) -> { 方法体 }
形式示例说明
无参() -> System.out.println("Hi")空括号必须保留
单参无类型x -> x * 2可省略括号和类型
单参有类型(String s) -> s.length()显式声明类型
多参(a, b) -> a + b括号必须保留
多行方法体(a, b) -> { int c = a + b; return c; }花括号和 return 必须

2.2 Lambda 的本质

Lambda 表达式不是匿名内部类的语法糖,它是通过 invokedynamic 指令在运行时动态生成实现类的。与匿名内部类关键区别:

维度匿名内部类Lambda
实现方式编译时生成 $1.class 文件运行时 invokedynamic 动态生成
this 指向指向匿名内部类自身指向外层类实例
作用域可定义新变量,会遮蔽外层不能定义与外部同名变量
性能每次创建新对象JVM 优化后可复用

三、函数式接口

3.1 什么是函数式接口

函数式接口:有且仅有一个抽象方法的接口。可以用 @FunctionalInterface 注解标记,编译器会检查是否满足条件。

java
@FunctionalInterface
public interface MyFunction {
    int apply(int a, int b);  // 只有一个抽象方法

    // 默认方法和静态方法不影响
    default void log() {
        System.out.println("Processing...");
    }

    static MyFunction identity() {
        return (a, b) -> a;
    }
}

3.2 四大核心函数式接口

Java 8 在 java.util.function 包中内置了 43 个函数式接口,其中四个最核心:

接口方法签名用途形象比喻
Function<T, R>R apply(T t)输入 T,输出 R,转换加工厂:原料进,产品出
Consumer<T>void accept(T t)消费一个值,无返回黑洞:吃进去,不吐出来
Supplier<T>T get()无输入,提供值供应商:要就给,从不问为什么
Predicate<T>boolean test(T t)判断条件法官:判定有罪/无罪
java
// Function:转换
Function<String, Integer> strLen = s -> s.length();
System.out.println(strLen.apply("Hello"));  // 5

// 函数组合:andThen(先执行自己,再执行参数)和 compose(先执行参数,再执行自己)
Function<Integer, String> intToStr = i -> "Result: " + i;
Function<String, Integer> composed = strLen.andThen(intToStr);  // 不合适,类型不匹配
// 正确示例:
Function<Integer, Integer> multiply10 = x -> x * 10;
Function<Integer, Integer> add2 = x -> x + 2;
Function<Integer, Integer> pipeline = multiply10.andThen(add2);
System.out.println(pipeline.apply(3));  // 3 * 10 + 2 = 32

// Consumer:消费
Consumer<String> printer = s -> System.out.println("打印: " + s);
Consumer<String> logger = s -> System.out.println("日志: " + s);
printer.andThen(logger).accept("Hello");  // 依次执行两个 Consumer

// Supplier:提供
Supplier<Double> random = Math::random;
System.out.println(random.get());  // 每次获取一个随机数

// Predicate:判断
Predicate<String> isEmpty = s -> s == null || s.isEmpty();
Predicate<String> isLong = s -> s.length() > 10;
System.out.println(isEmpty.negate().and(isLong).test("Hello World"));  // true
// negate() 取反, and() 与, or() 或

3.3 其他常用函数式接口

接口方法签名说明
BiFunction<T, U, R>R apply(T t, U u)双参数 Function
UnaryOperator<T>T apply(T t)一元运算,继承 Function,输入输出同类型
BinaryOperator<T>T apply(T t1, T t2)二元运算,继承 BiFunction
BiConsumer<T, U>void accept(T t, U u)双参数 Consumer
BiPredicate<T, U>boolean test(T t, U u)双参数 Predicate

四、方法引用(Method Reference)

方法引用是 Lambda 的简化写法,当 Lambda 体只是调用一个已有方法时使用。语法:类名/对象名::方法名

类型语法等价 Lambda示例
静态方法引用类名::静态方法(a) -> 类名.静态方法(a)Integer::parseInt
实例方法引用(特定对象)对象::实例方法() -> 对象.实例方法()System.out::println
实例方法引用(任意对象)类名::实例方法(a, b) -> a.方法(b)String::compareTo
构造方法引用类名::new() -> new 类名()ArrayList::new
java
// 静态方法引用
Function<String, Integer> parser = Integer::parseInt;
// 等价于: Function<String, Integer> parser = s -> Integer.parseInt(s);

// 特定对象的实例方法引用
Consumer<String> printer = System.out::println;
// 等价于: Consumer<String> printer = s -> System.out.println(s);

// 任意对象的实例方法引用(第一个参数成为方法调用者)
Comparator<String> comp = String::compareTo;
// 等价于: Comparator<String> comp = (a, b) -> a.compareTo(b);

// 构造方法引用
Supplier<List<String>> listFactory = ArrayList::new;
// 等价于: Supplier<List<String>> listFactory = () -> new ArrayList<>();

// 带参数的构造方法引用
Function<Integer, ArrayList<String>> sizedFactory = ArrayList::new;
// 等价于: Function<Integer, ArrayList<String>> f = n -> new ArrayList<>(n);

五、Stream API

Stream 是 Java 8 引入的数据处理抽象,不是数据结构,不存储数据。它像一个流水线,数据从源头流过,经过一系列中间操作,最终由终端操作收集结果。

5.1 Stream 的特性

[数据源] → [中间操作1] → [中间操作2] → [中间操作3] → [终端操作] → [结果]
 集合/数组     filter()       map()        sorted()       collect()
  (惰性求值)    (惰性求值)     (惰性求值)     (惰性求值)      (触发计算)

核心特性:

  • 惰性求值:中间操作不会立即执行,只有在终端操作触发时才计算
  • 链式调用:中间操作返回 Stream,可以链式组合
  • 不可复用:Stream 只能消费一次,终端操作后 Stream 关闭
  • 不修改数据源:所有操作返回新 Stream,原始数据不变
  • 内部迭代:由 Stream API 管理迭代,而非显式 for 循环

5.2 创建 Stream

java
// 1. 从集合创建
Stream<String> stream1 = list.stream();          // 顺序流
Stream<String> stream2 = list.parallelStream(); // 并行流

// 2. 从数组创建
Stream<Integer> stream3 = Arrays.stream(new Integer[]{1, 2, 3});
Stream<String> stream4 = Stream.of("a", "b", "c");

// 3. 无限流(配合 limit 使用)
Stream<Integer> stream5 = Stream.iterate(0, n -> n + 2);  // 0, 2, 4, 6, ...
Stream<Double> stream6 = Stream.generate(Math::random);   // 随机数流

// 4. 基本类型流
IntStream intStream = IntStream.range(1, 10);    // 1 到 9(不包含10)
IntStream intStream2 = IntStream.rangeClosed(1, 10); // 1 到 10(包含10)

// 5. 从文件创建
try (Stream<String> lines = Files.lines(Paths.get("file.txt"))) {
    lines.forEach(System.out::println);
}

5.3 中间操作(Intermediate Operations)

中间操作返回 Stream,惰性求值,不触发实际计算。

filter —— 过滤

java
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Amy");
// 过滤出长度大于3的名字
List<String> result = names.stream()
    .filter(s -> s.length() > 3)
    .collect(Collectors.toList());
// 结果: [Alice, Charlie, David]

map —— 转换(一对一)

java
// 提取所有名字的长度
List<Integer> lengths = names.stream()
    .map(String::length)
    .collect(Collectors.toList());
// 结果: [5, 3, 7, 5, 3]

// 提取对象属性
List<User> users = ...;
List<String> userNames = users.stream()
    .map(User::getName)
    .collect(Collectors.toList());

flatMap —— 扁平化(一对多)

java
// 将嵌套列表拍平
List<List<String>> nested = Arrays.asList(
    Arrays.asList("a", "b"),
    Arrays.asList("c", "d", "e")
);
List<String> flat = nested.stream()
    .flatMap(List::stream)
    .collect(Collectors.toList());
// 结果: [a, b, c, d, e]

// 经典场景:拆分单词
List<String> words = Arrays.asList("Hello World", "Java Stream");
List<String> letters = words.stream()
    .flatMap(s -> Arrays.stream(s.split("")))  // 每个单词拆成字符
    .distinct()
    .collect(Collectors.toList());
// 结果: [H, e, l, o, W, r, d, J, a, v, S, t, m]

distinct —— 去重

java
List<Integer> nums = Arrays.asList(1, 2, 2, 3, 3, 3);
List<Integer> unique = nums.stream()
    .distinct()
    .collect(Collectors.toList());
// 结果: [1, 2, 3]

sorted —— 排序

java
// 自然排序
List<String> sorted = names.stream().sorted().collect(Collectors.toList());

// 自定义比较器
List<String> sortedByLen = names.stream()
    .sorted((a, b) -> Integer.compare(a.length(), b.length()))
    .collect(Collectors.toList());
// 结果: [Bob, Amy, Alice, David, Charlie]

peek —— 调试(不改变元素)

java
// 查看流经每个元素的中间状态,常用于调试
List<String> result = names.stream()
    .peek(s -> System.out.println("处理前: " + s))
    .filter(s -> s.length() > 3)
    .peek(s -> System.out.println("过滤后: " + s))
    .collect(Collectors.toList());

limit / skip —— 截取

java
Stream<Integer> ints = Stream.iterate(0, n -> n + 1);

// limit:取前 n 个
ints.limit(5).forEach(System.out::print);  // 01234

// skip:跳过前 n 个
Stream.iterate(0, n -> n + 1)
    .skip(5).limit(3)
    .forEach(System.out::print);  // 567

// 分页场景
int pageSize = 10;
int pageNumber = 2;
List<User> page = users.stream()
    .skip((long) (pageNumber - 1) * pageSize)
    .limit(pageSize)
    .collect(Collectors.toList());

5.4 终端操作(Terminal Operations)

终端操作触发实际计算,消费 Stream 并返回结果。

collect —— 收集(最常用)

java
List<String> list = stream.collect(Collectors.toList());
Set<String> set = stream.collect(Collectors.toSet());
Map<String, Integer> map = stream.collect(
    Collectors.toMap(Function.identity(), String::length)
);

forEach / forEachOrdered

java
// forEach:遍历,不保证顺序(并行流时)
stream.forEach(System.out::println);

// forEachOrdered:保证顺序(即使并行流)
stream.forEachOrdered(System.out::println);

reduce —— 归约

java
// 求和
int sum = IntStream.range(1, 101).reduce(0, (a, b) -> a + b);
// 等价于
int sum2 = IntStream.range(1, 101).sum();

// 求最大值
Optional<Integer> max = Stream.of(1, 3, 5, 2, 4)
    .reduce((a, b) -> a > b ? a : b);

// 拼接字符串
String concat = Stream.of("Hello", " ", "World")
    .reduce("", (a, b) -> a + b);
// 结果: "Hello World"

count

java
long count = stream.filter(s -> s.length() > 3).count();

匹配操作

java
// anyMatch:至少有一个匹配
boolean hasLong = names.stream().anyMatch(s -> s.length() > 5);  // true

// allMatch:全部匹配
boolean allLong = names.stream().allMatch(s -> s.length() > 3);  // false

// noneMatch:一个都不匹配
boolean noneMatch = names.stream().noneMatch(s -> s.isEmpty());  // true

findFirst / findAny

java
// findFirst:返回第一个元素
Optional<String> first = names.stream()
    .filter(s -> s.startsWith("A"))
    .findFirst();  // Optional[Alice]

// findAny:返回任意一个元素(并行流更高效)
Optional<String> any = names.parallelStream()
    .filter(s -> s.startsWith("A"))
    .findAny();  // Optional[Alice] 或 Optional[Amy]

5.5 Stream 完整示例:传统 vs Stream

java
// 需求:从用户列表中筛选出成年用户,提取姓名,按字母排序,取前3个

// 传统方式(JDK 7)
List<String> result = new ArrayList<>();
for (User user : users) {
    if (user.getAge() >= 18) {
        result.add(user.getName());
    }
}
Collections.sort(result);
if (result.size() > 3) {
    result = result.subList(0, 3);
}
for (String name : result) {
    System.out.println(name);
}

// Stream 方式
users.stream()
    .filter(u -> u.getAge() >= 18)   // 筛选成年用户
    .map(User::getName)               // 提取姓名
    .sorted()                         // 排序
    .limit(3)                         // 取前3
    .forEach(System.out::println);    // 打印

六、Collectors 详解

6.1 基础收集器

java
// toList
List<String> list = stream.collect(Collectors.toList());

// toSet
Set<String> set = stream.collect(Collectors.toSet());

// toCollection:指定集合类型
TreeSet<String> treeSet = stream.collect(Collectors.toCollection(TreeSet::new));

// joining:字符串拼接
String joined = stream.collect(Collectors.joining(", "));
// 带前缀后缀
String joined2 = stream.collect(Collectors.joining(", ", "[", "]"));
// 结果: "[Alice, Bob, Charlie]"

6.2 toMap —— 收集为 Map

java
// 基本用法:key 是名字,value 是年龄
Map<String, Integer> map = users.stream()
    .collect(Collectors.toMap(User::getName, User::getAge));

// 处理 key 冲突:保留后者
Map<String, Integer> map2 = users.stream()
    .collect(Collectors.toMap(
        User::getName,
        User::getAge,
        (existing, replacement) -> replacement  // 冲突时用新值
    ));

// 指定 Map 类型
Map<String, Integer> map3 = users.stream()
    .collect(Collectors.toMap(
        User::getName,
        User::getAge,
        (e, r) -> e,
        TreeMap::new  // 指定 TreeMap
    ));

6.3 groupingBy —— 分组

java
// 按年龄分组
Map<Integer, List<User>> byAge = users.stream()
    .collect(Collectors.groupingBy(User::getAge));

// 分组后计数
Map<Integer, Long> countByAge = users.stream()
    .collect(Collectors.groupingBy(User::getAge, Collectors.counting()));

// 分组后提取属性
Map<Integer, List<String>> namesByAge = users.stream()
    .collect(Collectors.groupingBy(
        User::getAge,
        Collectors.mapping(User::getName, Collectors.toList())
    ));

// 多级分组:先按城市,再按性别
Map<String, Map<String, List<User>>> multiGroup = users.stream()
    .collect(Collectors.groupingBy(
        User::getCity,
        Collectors.groupingBy(User::getGender)
    ));

// 分组统计
Map<String, DoubleSummaryStatistics> statsByCity = users.stream()
    .collect(Collectors.groupingBy(
        User::getCity,
        Collectors.summarizingDouble(User::getSalary)
    ));

6.4 partitioningBy —— 分区

分区是分组的一种特例,partition 只有 true 和 false 两组。

java
// 按是否成年分两组
Map<Boolean, List<User>> partitioned = users.stream()
    .collect(Collectors.partitioningBy(u -> u.getAge() >= 18));

// 分区后统计
Map<Boolean, Long> count = users.stream()
    .collect(Collectors.partitioningBy(
        u -> u.getAge() >= 18,
        Collectors.counting()
    ));
// 结果: {false=15, true=85}

6.5 其他实用收集器

java
// summarizingInt:汇总统计(count, sum, min, average, max)
IntSummaryStatistics stats = users.stream()
    .collect(Collectors.summarizingInt(User::getAge));
System.out.println("平均年龄: " + stats.getAverage());
System.out.println("最大年龄: " + stats.getMax());

// maxBy / minBy:按比较器取最大/最小
Optional<User> eldest = users.stream()
    .collect(Collectors.maxBy(Comparator.comparingInt(User::getAge)));

// reducing:通用归约
Integer totalAge = users.stream()
    .collect(Collectors.reducing(0, User::getAge, Integer::sum));

七、并行流(Parallel Stream)

7.1 创建并行流

java
// 方式一:parallelStream()
list.parallelStream().filter(...).collect(...);

// 方式二:parallel()
list.stream().parallel().filter(...).collect(...);

// 方式三:并行转串行
list.parallelStream().sequential().filter(...).collect(...);

7.2 底层原理

并行流底层使用 ForkJoinPool 的公共线程池 ForkJoinPool.commonPool(),默认线程数为 CPU 核心数 - 1。通过 RecursiveTask 将大任务不断二分(fork),直到子任务足够小,然后合并(join)结果。

7.3 何时使用并行流

适合并行流不适合并行流
数据量大(10万+)数据量小(几千)
计算密集(CPU 密集型)IO 密集型
无状态、无副作用的操作有状态操作(如 sorted、distinct)
元素独立、无共享可变状态依赖顺序的操作(如 findFirst)
数据结构可高效拆分(ArrayList、数组)难以拆分的数据结构(LinkedList)

7.4 常见陷阱

java
// 陷阱1:在并行流中修改共享变量(线程不安全)
int[] sum = {0};
IntStream.range(0, 10000).parallel().forEach(i -> sum[0] += i);
// sum[0] 的值不确定,每次运行结果不同!

// 正确做法:使用 reduce
int correctSum = IntStream.range(0, 10000).parallel().sum();

// 陷阱2:并行流中使用有状态的操作
// 以下代码在并行流中行为不可预测
List<Integer> list = new ArrayList<>();
IntStream.range(0, 100).parallel().forEach(list::add);
// list 的大小可能不是 100,元素顺序不确定

// 陷阱3:阻塞操作耗尽公共线程池
// 不要在 parallelStream 中执行 IO 或网络请求
// 这会阻塞公共线程池,影响整个 JVM 的其他并行流

八、Optional

8.1 为什么需要 Optional?

null 是 Java 最臭名昭著的错误来源。Tony Hoare(null 的发明者)称其为"十亿美元的错误"。Optional 是一个容器类,它可能包含也可能不包含非 null 值,强制开发者显式处理"值不存在"的情况。

java
// 传统 null 检查 —— 防御式编程,层层嵌套
public String getCity(User user) {
    if (user != null) {
        Address address = user.getAddress();
        if (address != null) {
            City city = address.getCity();
            if (city != null) {
                return city.getName();
            }
        }
    }
    return "Unknown";
}

// Optional 链式调用 —— 扁平化,可读性高
public String getCity(User user) {
    return Optional.ofNullable(user)
        .map(User::getAddress)
        .map(Address::getCity)
        .map(City::getName)
        .orElse("Unknown");
}

8.2 创建 Optional

java
// 创建非空 Optional —— 值为 null 时抛 NullPointerException
Optional<String> opt1 = Optional.of("Hello");

// 创建可空 Optional —— 值为 null 时返回 Optional.empty()
Optional<String> opt2 = Optional.ofNullable(maybeNull);

// 创建空 Optional
Optional<String> opt3 = Optional.empty();

8.3 获取值

java
Optional<String> opt = Optional.of("Hello");

// get():获取值,若为空抛 NoSuchElementException(不推荐直接使用)
String val = opt.get();  // "Hello"

// orElse:有值返回值,无值返回默认值(无论有无值,默认值都会执行)
String val2 = opt.orElse(expensiveOperation());  // expensiveOperation 总会执行

// orElseGet:有值返回值,无值执行 Supplier(惰性求值,推荐)
String val3 = opt.orElseGet(() -> expensiveOperation());  // 仅在需要时执行

// orElseThrow:有值返回值,无值抛异常
String val4 = opt.orElseThrow(() -> new IllegalArgumentException("值不存在"));

// ifPresent:有值时执行操作
opt.ifPresent(v -> System.out.println("Value: " + v));

8.4 转换与过滤

java
// map:转换值
Optional<Integer> length = Optional.of("Hello")
    .map(String::length);  // Optional[5]

// flatMap:防止嵌套 Optional
public class User {
    public Optional<Address> getAddress() { ... }
}
Optional<User> user = Optional.of(new User());
Optional<String> city = user
    .flatMap(User::getAddress)   // 如果用 map 会得到 Optional<Optional<Address>>
    .map(Address::getCity);

// filter:条件过滤
Optional<String> filtered = Optional.of("Hello")
    .filter(s -> s.length() > 10);  // 不满足条件,返回 Optional.empty()

8.5 Optional 使用原则

推荐不推荐
作为方法返回值,表示"可能没有结果"作为字段类型
用于链式调用避免 NPE作为方法参数
使用 orElseGet 获取默认值在集合中使用(List<Optional<T>>
使用 ifPresent 执行副作用直接调用 get() 而不检查

九、基本类型流(Primitive Streams)

Java 8 为三种基本类型提供了专门的 Stream:IntStreamLongStreamDoubleStream,避免装箱/拆箱开销。

java
// 创建基本类型流
IntStream intStream = IntStream.range(1, 100);           // 1-99
IntStream intStream2 = IntStream.rangeClosed(1, 100);   // 1-100
DoubleStream doubleStream = DoubleStream.of(1.0, 2.0, 3.0);

// 基本类型特有方法
int sum = IntStream.range(1, 101).sum();                // 求和
OptionalDouble avg = IntStream.range(1, 101).average();  // 平均值
OptionalInt max = IntStream.range(1, 101).max();         // 最大值
IntSummaryStatistics stats = IntStream.range(1, 101).summaryStatistics();

// Stream 与基本类型流的转换
Stream<Integer> boxed = IntStream.range(1, 10).boxed();         // 装箱
IntStream unboxed = Stream.of(1, 2, 3).mapToInt(Integer::intValue);  // 拆箱
LongStream longStream = Stream.of("1", "2").mapToLong(Long::parseLong);

// 数值范围转数组
int[] array = IntStream.range(1, 10).toArray();

十、Stream 性能注意事项

java
// 1. 避免多次遍历 —— 一次终端操作触发一次遍历
// 反例:多次遍历
List<String> list = ...;
List<String> filtered = list.stream().filter(...).collect(toList());  // 遍历1
List<Integer> mapped = list.stream().map(...).collect(toList());       // 遍历2

// 3. 正确选择中间操作顺序
// 反例:先 map 后 filter —— map 对所有元素执行
list.stream().map(expensiveOp).filter(condition).collect(toList());
// 正例:先 filter 后 map —— 减少 map 执行次数
list.stream().filter(condition).map(expensiveOp).collect(toList());

// 4. 短路操作放在前面
// limit 放在前面可以减少后续操作量
list.stream().limit(10).filter(...).collect(toList());

// 5. 避免在 Stream 中抛受检异常
// 反例:编译错误
list.stream().map(s -> {
    return new String(s.getBytes(), "UTF-8");  // UnsupportedEncodingException
});
// 正例:封装为运行时异常或使用工具方法
list.stream().map(s -> {
    try {
        return new String(s.getBytes(), "UTF-8");
    } catch (UnsupportedEncodingException e) {
        throw new RuntimeException(e);
    }
});

十一、面试要点

Q1: Lambda 表达式和匿名内部类的区别?

Lambda 底层通过 invokedynamic 动态生成实现类,匿名内部类在编译时生成独立的 .class 文件。Lambda 中 this 指向外层类,匿名内部类中 this 指向内部类自身。Lambda 不能定义与外部同名的局部变量。Lambda 不能创建新的作用域,匿名内部类可以。

Q2: 什么是函数式接口?@FunctionalInterface 注解的作用?

函数式接口是有且仅有一个抽象方法的接口(可以有多个 default 或 static 方法)。@FunctionalInterface 是可选注解,作用是在编译期检查接口是否符合函数式接口定义,如果接口有多个抽象方法,编译器会报错。

Q3: Stream 的中间操作和终端操作有什么区别?

中间操作(filter, map, sorted 等)返回 Stream,惰性求值,不触发实际计算。终端操作(collect, forEach, reduce 等)触发计算,消费 Stream 并返回非 Stream 结果。Stream 在终端操作后关闭,不可复用。

Q4: map 和 flatMap 的区别?

维度mapflatMap
映射关系一对一一对多
返回值Stream<R>Stream<R>(将多个 Stream 拍平为一个)
典型场景提取对象属性拆分字符串、展开嵌套集合
java
// map 返回 Stream<Stream<String>>(嵌套)
Stream<Stream<String>> nested = words.stream().map(w -> Arrays.stream(w.split("")));
// flatMap 返回 Stream<String>(拍平)
Stream<String> flat = words.stream().flatMap(w -> Arrays.stream(w.split("")));

Q5: 并行流什么时候用?有什么坑?

适合 CPU 密集型、数据量大(10万+)、元素独立无共享状态、数据结构可高效拆分(ArrayList、数组)的场景。常见坑:并行流中修改共享变量导致线程不安全;有状态操作(sorted、distinct)性能下降;阻塞操作耗尽 ForkJoinPool 公共线程池;LinkedList 等难以拆分的数据结构性能差。

Q6: Optional 的 orElse 和 orElseGet 有什么区别?

orElse 的参数无论 Optional 是否有值都会执行,orElseGet 的 Supplier 参数仅在 Optional 为空时才执行。因此当默认值计算成本高时,应使用 orElseGet 实现惰性求值。

java
// orElse:expensiveOp() 始终执行
String result = opt.orElse(expensiveOp());

// orElseGet:expensiveOp() 仅在需要时执行
String result = opt.orElseGet(() -> expensiveOp());

Q7: Collectors.groupingBy 和 partitioningBy 的区别?

groupingBy 按任意分类函数分组,可以产生多个组(Map 的 key 是分组键)。partitioningBy 是 groupingBy 的特例,只能按 boolean 条件分为 true 和 false 两组,返回 Map<Boolean, List<T>>。partitioningBy 效率更高,因为只需要两个桶。

Q8: Stream 是如何实现惰性求值的?

Stream 的中间操作(如 filter、map)不会立即执行,而是将操作记录在内部的一个双向链表(Sink 链)中。当终端操作触发时,数据从源头开始,依次经过每个中间操作的 Sink 节点,最终到达终端操作。这种设计使得短路操作(如 limit、findFirst)可以在满足条件时提前终止遍历,节省计算资源。

Q9: 为什么 Stream 中的 sorted 操作可能会影响性能?

sorted() 是无状态操作转为有状态操作的分界线。它需要先收集所有元素,然后排序,再继续流式处理。这意味着在无限流上不能使用 sorted,并且在大数据量并行流中,sorted 的排序合并开销很大。如果不需要全局排序,可以考虑使用 unordered() 解除排序约束以提升并行流性能。