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 就更快”:先量测数据规模、任务成本和共享状态;并行执行中的副作用很容易导致错误。
自测
- 只调用
stream().filter(...)不调用终止操作会执行过滤吗?不会。 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 输入策略并比较可读性。