跳转至

Stream 与 Optional 增强#

版本信息

为什么需要#

JDK 8 的 Stream 只能 filter(保留全部满足条件的),没法表达「取满足条件的前缀、一旦中断就停」(如读一段正数直到第一个负数)。Optional 也缺胳膊少腿:判空只有 isPresent(),写否定要 !opt.isPresent()(别扭);没有「有值/无值分别处理」的一体化方法,被迫写 if-else;没有「为空则给另一个 Optional」的链式默认值,只能 orElse(且 orElse 的实参总是被求值)。JDK 9/11 一次性补齐这些日常痛点。

语法#

examples/stream-optional/真实可运行的源码(snippets 嵌入,与示例零漂移):

package com.javamodern.streamopt;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

// JDK 9/11 给 Stream 与 Optional 补的日常方法:
//   Stream.takeWhile / dropWhile(JDK 9)
//   Optional.isEmpty(JDK 11)、Optional.ifPresentOrElse / or / stream(JDK 9)
public class StreamOptionalDemo {

    /** Stream.takeWhile(JDK 9):取「满足谓词的最长前缀」,遇到第一个不满足即停。 */
    public static List<Integer> takeWhilePositive(List<Integer> nums) {
        return nums.stream().takeWhile(n -> n > 0).collect(Collectors.toList());   // (1)
    }

    /** Stream.dropWhile(JDK 9):丢「满足谓词的最长前缀」,从第一个不满足处开始保留。 */
    public static List<Integer> dropWhilePositive(List<Integer> nums) {
        return nums.stream().dropWhile(n -> n > 0).collect(Collectors.toList());
    }

    /** Optional.isEmpty(JDK 11):与 isPresent 互补,判空更直观。 */
    public static boolean isAbsent(Optional<String> opt) {
        return opt.isEmpty();
    }

    /** Optional.ifPresentOrElse(JDK 9):有值/无值分别处理,免去外部 if-else。 */
    public static String describe(Optional<String> opt) {
        StringBuilder sb = new StringBuilder();
        opt.ifPresentOrElse(v -> sb.append("有:").append(v), () -> sb.append("无"));
        return sb.toString();
    }

    /** Optional.or(JDK 9):当前为空则提供另一个 Optional(链式默认值)。 */
    public static String valueOr(Optional<String> opt, String fallback) {
        return opt.or(() -> Optional.of(fallback)).get();
    }

    public static void main(String[] args) {
        System.out.println("takeWhile(1,2,-1,3) = " + takeWhilePositive(List.of(1, 2, -1, 3)));
        System.out.println("dropWhile(1,2,-1,3) = " + dropWhilePositive(List.of(1, 2, -1, 3)));
        System.out.println("isEmpty(empty) = " + isAbsent(Optional.empty()));
        System.out.println("describe(present) = " + describe(Optional.of("Java")));
        System.out.println("describe(absent) = " + describe(Optional.empty()));
        System.out.println("valueOr(empty,默认) = " + valueOr(Optional.empty(), "默认"));
    }
}
  1. :material-lightbulb: takeWhile/dropWhile前缀谓词操作(遇第一个不满足即停,区别于 filter 的全量筛选);Optional.isEmpty()(JDK 11)与 isPresent() 互补;ifPresentOrElse/or 让 Optional 的分支与默认值链式化。

运行 StreamOptionalDemo.main 的实测输出(JDK 11):

takeWhile(1,2,-1,3) = [1, 2]
dropWhile(1,2,-1,3) = [-1, 3]
isEmpty(empty) = true
describe(present) = 有:Java
describe(absent) = 无
valueOr(empty,默认) = 默认

与 JDK 8 旧写法对比#

// 取「正数前缀」要手写循环或借助 iterate/spliterator,很绕
List<Integer> prefix = new ArrayList<>();
for (int n : nums) { if (n > 0) prefix.add(n); else break; }
// Optional 否定判空:!
boolean absent = !opt.isPresent();
// 有/无值分支:显式 if-else
String d = opt.isPresent() ? "有:" + opt.get() : "无";
// 默认值:orElse 实参总被求值(即使 opt 有值)
String v = opt.orElse(loadExpensiveDefault());
List<Integer> prefix = nums.stream().takeWhile(n -> n > 0).collect(toList());
boolean absent = opt.isEmpty();                       // JDK 11,更直观
opt.ifPresentOrElse(v -> ..., () -> ...);             // 有/无值分别处理
String v = opt.or(() -> Optional.of(loadDefault()))   // 惰性:仅空时才求值
               .orElseThrow();

底层原理#

  • takeWhile/dropWhile短路、有状态的中间操作:在有序流上扫描元素,一旦谓词「翻转」即停止处理后续(区别于 filter 会扫完全部)。在无序流上语义退化为「任一满足/不满足子集」。
  • Optional.or(Supplier) 的回退是惰性的——Supplier 只在当前 Optional 为空时才调用;而老的 orElse(value)value 是方法实参,无论是否为空都会先求值(这是「有副作用/昂贵默认值」的经典坑,要改用 orElseGet(Supplier)or)。
graph LR
    S["Stream"] --> TW["takeWhile: 取满足谓词的前缀<br/>遇中断即停(短路)"]
    S --> DW["dropWhile: 丢满足谓词的前缀"]
    S -.filter 全量扫描(对比).- F["filter"]
    O["Optional"] --> E["isEmpty(JDK 11)"]
    O --> I["ifPresentOrElse:有/无值双分支"]
    O --> R["or(Supplier):惰性回退"]
    style TW fill:#bbdefb
    style R fill:#c8e6c9

常见坑 / 最佳实践#

  • takeWhile/dropWhile 是「前缀」操作,不是「筛选」[1,2,-1,3].takeWhile(>0)[1,2]不会把后面的 3 也捞回来——要全量匹配用 filter
  • 无序流语义不同:无序流上 takeWhile/dropWhile 的结果是「某个」满足子集,行为不确定;要稳定结果先用顺序流。
  • isEmpty()(JDK 11)别和 isPresent() 混写:判否定优先 isEmpty(),可读性更好;旧代码用 !isPresent() 的可顺手替换。
  • orElse vs orElseGet vs or:回退值昂贵或有副作用时,用 orElseGet(Supplier)or(Supplier)——orElse(expensive) 即使值存在也会算一遍。
  • Optional.stream():把 Optional<T> 转成 0/1 元素的 Stream<T>,便于 flatMap 链式过滤掉空值。

小结#

takeWhile/dropWhile 填上了 Stream「按前缀操作」的缺口,isEmpty/ifPresentOrElse/or 让 Optional 的判空、分支、默认值链式化且惰性——都是 JDK 9/11 里高频、低风险、即用即得的标准库改进。

参考#