← Backend / Java

16_Java Lambda Stream Optional

函数式接口、Stream 惰性操作、Optional 及副作用控制。

Java Lambda、Stream 与 Optional

学习目标:用函数式接口和 Stream 表达集合变换,并理解延迟执行与副作用的边界。

1. Lambda 是行为值

函数式接口只有一个抽象方法。Lambda 可以作为该接口的实现传入方法,例如 Predicate<T> 用于判断,Function<T,R> 用于转换。

import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> names = List.of("Alice", " Bob ", "Anna");
        List<String> result = names.stream()
                .map(String::trim)
                .filter(name -> name.startsWith("A"))
                .toList();
        System.out.println(result); // [Alice, Anna]
    }
}

map、filter 等中间操作是惰性的;直到 toList、count、collect 等终止操作才执行。Stream 是一次性流水线,不能终止后重复使用。Stream.toList() 返回不可修改的列表。

2. Optional 表示可能缺失

import java.util.Optional;

class Lookup {
    static Optional<String> findName(int id) {
        return id == 1 ? Optional.of("Alice") : Optional.empty();
    }

    static String label(int id) {
        return findName(id).map(String::toUpperCase).orElse("UNKNOWN");
    }
}

Optional 常用作“可能找不到”的返回值,不是所有字段和方法参数的通用替代品。orElse(value) 会先计算 value;若默认值构造昂贵,使用 orElseGet(supplier)。

3. 何时不用 Stream

多步修改外部状态、需要 break 或复杂异常处理时,普通 for 循环通常更清楚。并行 Stream 不是“加上 parallel 就更快”:先量测数据规模、任务成本和共享状态;并行执行中的副作用很容易导致错误。

易错点:避免在 `map`、`filter` 中修改外部集合;流水线应尽量保持无副作用。

自测

  1. 只调用 stream().filter(...) 不调用终止操作会执行过滤吗?不会。
  2. orElse 与 orElseGet 在默认值计算上有何区别?前者立即求值,后者按需调用。

4. 理解流水线,而不只记方法名

filter 保留满足条件的元素,map 一对一转换,flatMap 把每个元素映射成流再展开。中间操作在终止操作发生时才执行,且 Stream 只能消费一次。需要同时保留排序和去重时,应明确顺序语义与开销;distinct() 依赖元素的 equals/hashCode。

import java.util.List;

class Tags {
    static List<String> normalized(List<String> raw) {
        return raw.stream()
                .map(String::trim)
                .filter(tag -> !tag.isEmpty())
                .distinct()
                .toList();
    }
}

以上代码按首次出现顺序得到去重标签。若 raw 可能含 null,先定义如何处理,不要让 String::trim 意外抛空指针。业务中涉及外部 I/O 或可变共享状态时,普通循环常比 Stream 更容易看出失败和执行顺序。

5. Optional 的合适边界

Optional<T> 适合方法返回“可能没有结果”;Optional.empty() 不等于“操作失败”,数据库异常应继续用错误路径处理。不要用 Optional.get() 代替检查;可用 orElseThrow 在确实必须存在时给出明确异常。orElse(expensive()) 即使有值也会执行 expensive(),需要惰性默认值时用 orElseGet(() -> expensive())。

练习:把标签清理函数分别用 for 循环和 Stream 写一遍,再加入 null 输入策略并比较可读性。