乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Java概述与环境搭建

    • Java概述与环境搭建
    • Java语言概述
    • 解释型语言与编译型语言对比
    • JDK安装与配置
    • JDK、JRE、JVM 详解
    • HelloWorld程序详解
    • IDE 介绍
  • 第2章 标识符与基本数据类型

    • 章节导读
    • 变量概述
    • 常量概述
    • 基本类型与包装类
    • 字节型 byte
    • 短整型 short
    • 整型 int
    • 长整型 long
    • 单精度浮点型 float
    • 双精度浮点型 double
    • 字符型 char
    • 布尔型 boolean
    • 类型转换
  • 第3章 运算符与表达式

    • 章节导读
    • 算术运算符
    • 赋值运算符
    • 关系运算符
    • 逻辑运算符
    • 位运算符
    • 条件运算符
    • 运算符优先级
    • 表达式
  • 第4章 流程控制

    • 章节导读
    • 常见的程序运行流程
    • if-else 选择结构
    • switch 多分支选择
    • while 循环
    • do-while 循环
    • for 循环
    • break 与 continue
  • 第5章 数组

    • 章节导读
    • 一维数组
    • 多维数组
    • Arrays 工具类
  • 第6章 类与对象

    • 章节导读
    • 类与对象
    • 方法定义与调用
    • 构造方法
    • 封装
    • 访问修饰符
    • package 与 import
    • static 关键字
    • this 关键字
    • 参数传递 详解
    • 枚举
    • 成员内部类
    • 局部内部类
    • 静态内部类
    • 匿名内部类
  • 第7章 接口与继承

    • 章节导读
    • 继承
    • super 关键字
    • final 关键字
    • 多态
    • 向上转型与向下转型
    • 抽象类
    • 接口
    • 抽象类与接口对比
  • 第8章 注解

    • 章节导读
    • 注解基础
    • 元注解详解
    • 自定义注解
  • 第9章 常用类

    • 章节导读:Java 常用类
    • Object 类:万类之祖
    • 包装类:基本类型的对象化
    • String:不可变的字符串
    • StringBuffer:线程安全的可变字符串
    • StringBuilder:可变的字符串构建器
    • Math:数学运算工具类
    • Random:伪随机数生成器
    • 大数值运算 详解
    • 日期时间API 详解
  • 第10章 异常机制

    • 章节导读
    • 异常体系与分类
    • try-catch-finally
    • try-with-resources
    • throws 与 throw
    • 自定义异常
  • 第11章 泛型

    • 章节导读
    • 泛型基础
    • 通配符与PECS原则
    • 类型擦除
  • 第12章 集合框架

    • 章节导读
    • 集合框架概述
    • ArrayList
    • LinkedList
    • HashMap 详解
    • LinkedHashMap 详解
    • TreeMap 详解
    • HashSet
    • TreeSet 详解
    • TreeSet 与 Comparable
    • Collections 工具类详解
  • 第13章 IO流

    • 章节导读
    • IO流概述
    • 字节流
    • 字符流
    • 缓冲流
    • 转换流 详解
    • 序列化 详解
    • NIO与Files 详解
    • NIO与Files工具类
  • 第14章 多线程与并发

    • 第十六章 多线程与并发 —— 章节导读
    • 线程基础详解
    • synchronized 详解
    • Lock 与显式锁详解
    • volatile 详解
    • wait 与 notify 详解
    • ThreadLocal详解
    • 原子类详解
    • 并发工具类详解
    • 线程池详解
  • 第15章 反射

    • 章节导读
    • 反射概述与 Class 对象
    • Constructor 与对象创建
    • Field与Method详解
    • 反射应用详解
  • 第16章 JDK8新特性

    • 章节导读
    • Lambda 表达式
    • Stream API 基础
    • Stream API 高级详解
    • Optional 详解
    • 新日期时间API详解
  • 第17章 JDK9-11新特性

    • 章节导读
    • 模块化系统 — Project Jigsaw(JDK 9)
    • var 局部变量类型推断(JDK 10)
    • 集合工厂方法与增强(JDK 9 / 10 / 11)
    • 接口增强:private 方法(JDK 9)
    • Stream API 增强(JDK 9)
    • Optional 增强(JDK 9 / 10 / 11)
    • String 新增方法(JDK 11)
    • HTTP Client 与 Files 增强(JDK 11)
    • 直接运行 Java 源文件 — JEP 330(JDK 11)
  • 第18章 JDK12-17新特性

    • 章节导读
    • Switch 表达式(JDK 12 预览 / JDK 14 正式)
    • 文本块 Text Blocks(JDK 13 预览 / JDK 15 正式)
    • Records 记录类(JDK 14 预览 / JDK 16 正式)
    • 密封类 Sealed Classes(JDK 15 预览 / JDK 17 正式)
    • instanceof 模式匹配(JDK 14 预览 / JDK 16 正式)
    • Switch 模式匹配 — Pattern Matching for switch(JDK 17 预览 / JDK 21 正式)
    • Helpful NPE 与 String 增强(JDK 12 / JDK 14 / JDK 15)
    • Stream 增强(JDK 12 / JDK 16)
    • 日期时间增强 — Day Period 支持(JDK 16)
  • 第19章 JDK18-21新特性

    • 章节导读
    • 虚拟线程(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 序列集合(JDK 21 正式)
    • Switch 模式匹配(JDK 17 预览 / JDK 18 第二预览 / JDK 20 第四预览 / JDK 21 正式)
    • Record 模式匹配(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 未命名模式与变量(JDK 21 预览 / JDK 22 正式)
  • 第20章 JDK 22-25 新特性

    • 章节导读
    • 字符串模板(JDK 22 预览 / JDK 23 第二预览 / JDK 24 第三预览)
    • Stream Gatherers(JDK 22 预览 / JDK 24 第二预览)
    • 隐式声明类与实例方法(JDK 23 预览 / JDK 24 第二预览)
    • 原始类型模式匹配(JDK 24 预览)
  • 附录

    • Java 核心知识点
    • Java SE 专业术语
    • Java特性索引(JDK 8 → 25)

Optional 增强(JDK 9 / 10 / 11)

飞翔科技的监控系统告警了:线上出现了 NullPointerException。小崔急忙打开日志追踪,发现是一段 Optional 使用不当的代码。

Optional<Employee> emp = employeeDao.findById(id);
if (emp.isPresent()) {
    sendNotification(emp.get());
} else {
    logError("员工不存在: " + id);
}

白歌在事故复盘会上说:"这段代码有 JDK 8 的壳,没有 JDK 8 的魂。isPresent() + get() 是反模式——但真正的问题在于:即使你想做得对,JDK 8 的 Optional 确实缺少一些关键方法。JDK 9-11 就是来补这些窟窿的。"


定义表:JDK 9-11 新增方法一览

方法版本签名功能等效 JDK 8 写法
ifPresentOrElseJDK 9(Consumer<T>, Runnable)值存在执行 Consumer,不存在执行 Runnableif (opt.isPresent()) { ... } else { ... }
orJDK 9(Supplier<Optional<T>>)值存在返回自身,否则返回备选 Optional 的结果opt.isPresent() ? opt : fallback.get()
streamJDK 9()将 Optional 转为 0 或 1 个元素的 Streamopt.isPresent() ? Stream.of(opt.get()) : Stream.empty()
orElseThrow()JDK 10()值存在返回,否则抛出 NoSuchElementExceptionopt.orElseThrow(() -> new NoSuchElementException())
isEmptyJDK 11()判断值是否为空!opt.isPresent()

Mermaid 流程图:JDK 9-11 Optional 方法全景


ifPresentOrElse — 告别 isPresent + get

3.1 JDK 8 的痛点 vs JDK 9 的解法

import java.util.Optional;

public class IfPresentOrElseDemo {
    public static void main(String[] args) {
        // === 场景:飞翔科技员工查询 ===
        Optional<String> emp1 = findEmployee(1001);  // 存在
        Optional<String> emp2 = findEmployee(9999);  // 不存在

        // --- JDK 8 写法:if-else + isPresent + get(繁琐) ---
        if (emp1.isPresent()) {
            System.out.println("找到员工: " + emp1.get());
        } else {
            System.out.println("员工不存在");
        }

        // --- JDK 9 写法:一行搞定,优雅且无 get() ---
        emp1.ifPresentOrElse(
            name -> System.out.println("找到员工: " + name),
            ()   -> System.out.println("员工不存在")
        );

        emp2.ifPresentOrElse(
            name -> System.out.println("找到员工: " + name),
            ()   -> System.out.println("员工不存在(日志已记录)")
        );
    }

    static Optional<String> findEmployee(int id) {
        if (id == 1001) return Optional.of("大翔");
        if (id == 1002) return Optional.of("白歌");
        return Optional.empty();
    }
}

输出:

找到员工: 大翔
找到员工: 大翔
员工不存在(日志已记录)

3.2 实战:缓存查询的双分支处理

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

public class CacheQueryDemo {
    // 模拟缓存和数据库
    static Map<Integer, String> cache = new HashMap<>();
    static {
        cache.put(1001, "大翔");
        cache.put(1002, "白歌");
    }

    public static void main(String[] args) {
        queryEmployee(1001);
        queryEmployee(1003);
    }

    static void queryEmployee(int id) {
        System.out.println("--- 查询员工 " + id + " ---");

        Optional.ofNullable(cache.get(id))
                .ifPresentOrElse(
                    // 缓存命中:快速返回
                    name -> System.out.println("缓存命中: " + name),
                    // 缓存未命中:查数据库 + 记录日志
                    () -> {
                        System.out.println("缓存未命中,查询数据库...");
                        String dbResult = queryDatabase(id);
                        if (dbResult != null) {
                            cache.put(id, dbResult);  // 回填缓存
                        }
                        System.out.println("最终结果: " + dbResult);
                    }
                );
    }

    static String queryDatabase(int id) {
        // 模拟数据库,id=1003 存在,其他不存在
        return id == 1003 ? "孔蓝" : null;
    }
}

输出:

--- 查询员工 1001 ---
缓存命中: 大翔
--- 查询员工 1003 ---
缓存未命中,查询数据库...
最终结果: 孔蓝

or — 链式备选 Optional

4.1 JDK 8 的备选困境

import java.util.Optional;

public class OrDemo {
    public static void main(String[] args) {
        // === 三级缓存查询:L1 → L2 → 数据库 ===
        Optional<String> result1 = queryL1Cache(1001);
        Optional<String> result2 = queryL1Cache(9999);

        // --- JDK 8 写法:层层嵌套 orElseGet ---
        // 问题:每一层都返回 T 而不是 Optional<T>,丢失了空值语义
        String emp1 = result1.orElseGet(() ->
            queryL2Cache(1001).orElseGet(() ->
                queryDatabase(1001).orElse("未知员工")
            )
        );

        // --- JDK 9 写法:链式 or(),保持 Optional 管道 ---
        Optional<String> emp2 = result2
                .or(() -> queryL2Cache(9999))
                .or(() -> queryDatabase(9999));

        System.out.println("JDK 8方式: " + emp1);
        System.out.println("JDK 9方式: " + emp2.orElse("未知员工"));
    }

    static Optional<String> queryL1Cache(int id) {
        return id == 1001 ? Optional.of("大翔(L1)") : Optional.empty();
    }

    static Optional<String> queryL2Cache(int id) {
        return id == 1001 ? Optional.of("大翔(L2)") : Optional.empty();
    }

    static Optional<String> queryDatabase(int id) {
        return Optional.empty();
    }
}

输出:

JDK 8方式: 大翔(L1)
JDK 9方式: 未知员工

4.2 or vs orElseGet 的本质区别

import java.util.Optional;

public class OrVsOrElseGetDemo {
    public static void main(String[] args) {
        Optional<String> present = Optional.of("飞翔科技");
        Optional<String> empty = Optional.empty();

        // === orElseGet:返回 T(拆箱) ===
        String val1 = empty.orElseGet(() -> {
            System.out.println("orElseGet 执行了");
            return "默认值";
        });
        System.out.println("orElseGet 结果: " + val1);
        System.out.println("类型: " + val1.getClass().getSimpleName());

        // === or:返回 Optional<T>(保持装箱) ===
        Optional<String> val2 = empty.or(() -> {
            System.out.println("or() 执行了");
            return Optional.of("备选值");
        });
        System.out.println("\nor() 结果: " + val2);
        System.out.println("类型: " + val2.getClass().getSimpleName());
        // 还能继续链式操作!
        String finalResult = val2.map(String::toUpperCase).orElse("最终默认");
        System.out.println("链式转换后: " + finalResult);
    }
}

输出:

orElseGet 执行了
orElseGet 结果: 默认值
类型: String

or() 执行了
or() 结果: Optional[备选值]
类型: Optional
链式转换后: 备选值

核心差异:orElseGet 返回"值本身"(终结操作),or() 返回"Optional"(继续链式)。当你有多级备选时,or() 让管道不中断。


stream() — Optional 到 Stream 的桥梁

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

public class OptionalStreamDemo {
    public static void main(String[] args) {
        // === 场景:批量查询员工,只保留有效结果 ===
        List<Integer> ids = List.of(1001, 1002, 9998, 1003, 9999);

        // --- JDK 8 写法:先 filter 再 map(get) ---
        List<String> result1 = ids.stream()
                .map(OptionalStreamDemo::findEmployee)
                .filter(Optional::isPresent)
                .map(Optional::get)          // ⚠️ get() 是不安全的信号
                .collect(Collectors.toList());
        System.out.println("JDK 8: " + result1);

        // --- JDK 9 写法:flatMap(Optional::stream) ---
        List<String> result2 = ids.stream()
                .map(OptionalStreamDemo::findEmployee)
                .flatMap(Optional::stream)   // ✅ 优雅!自动跳过 empty
                .collect(Collectors.toList());
        System.out.println("JDK 9: " + result2);

        // === 原理解析:Optional.stream() 做了什么 ===
        Optional<String> present = Optional.of("大翔");
        Optional<String> empty = Optional.empty();

        Stream<String> s1 = present.stream();  // 流中有1个元素:"大翔"
        Stream<String> s2 = empty.stream();    // 空流

        System.out.println("\npresent.stream().count() = " + s1.count());  // 1
        System.out.println("empty.stream().count() = " + s2.count());      // 0
    }

    static Optional<String> findEmployee(int id) {
        return switch (id) {
            case 1001 -> Optional.of("大翔");
            case 1002 -> Optional.of("白歌");
            case 1003 -> Optional.of("孔蓝");
            default   -> Optional.empty();
        };
    }
}

输出:

JDK 8: [大翔, 白歌, 孔蓝]
JDK 9: [大翔, 白歌, 孔蓝]

present.stream().count() = 1
empty.stream().count() = 0

orElseThrow() 无参版本(JDK 10)

import java.util.Optional;

public class OrElseThrowDemo {
    public static void main(String[] args) {
        // --- JDK 8:必须提供异常 Supplier ---
        Optional<String> opt1 = Optional.empty();
        // opt1.orElseThrow(() -> new IllegalArgumentException("值不存在"));

        // --- JDK 10:无参版本,默认抛 NoSuchElementException ---
        Optional<String> opt2 = Optional.of("飞翔科技");
        String result = opt2.orElseThrow();  // 简洁!
        System.out.println("结果: " + result);

        // 有值时与 get() 等价,但语义更清晰
        // get() 在 empty 时不友好,orElseThrow() 明确表达了"期望非空"

        // --- 演示异常 ---
        try {
            Optional<String> empty = Optional.empty();
            empty.orElseThrow();  // → NoSuchElementException: No value present
        } catch (Exception e) {
            System.out.println("异常类型: " + e.getClass().getSimpleName());
            System.out.println("异常消息: " + e.getMessage());
        }
    }
}

输出:

结果: 飞翔科技
异常类型: NoSuchElementException
异常消息: No value present

isEmpty() — 更可读的空判断(JDK 11)

import java.util.Optional;

public class IsEmptyDemo {
    public static void main(String[] args) {
        Optional<String> present = Optional.of("飞翔科技");
        Optional<String> empty = Optional.empty();

        // --- JDK 8:只能用 !isPresent() ---
        System.out.println("JDK 8风格:");
        if (!present.isPresent()) System.out.println("不存在");
        if (!empty.isPresent())   System.out.println("不存在");

        // --- JDK 11:isEmpty() 更自然 ---
        System.out.println("\nJDK 11风格:");
        if (present.isEmpty()) System.out.println("不存在");
        if (empty.isEmpty())   System.out.println("不存在");

        // === 实际场景:参数校验 ===
        validateParam(Optional.of("大翔"));
        validateParam(Optional.empty());
    }

    static void validateParam(Optional<String> param) {
        if (param.isEmpty()) {
            System.out.println("❌ 参数缺失");
        } else {
            System.out.println("✅ 参数有效: " + param.get());
        }
    }
}

输出:

JDK 8风格:
不存在
JDK 11风格:
不存在
✅ 参数有效: 大翔
❌ 参数缺失

易错场景

8.1 ifPresentOrElse 的 Runnable 中不能抛受检异常

import java.util.Optional;

public class IfPresentOrElsePitfall {
    public static void main(String[] args) {
        Optional<String> empty = Optional.empty();

        // ❌ Runnable 的 run() 方法签名没有 throws
        // empty.ifPresentOrElse(
        //     System.out::println,
        //     () -> { throw new Exception("错误"); }  // 编译错误
        // );

        // ✅ 正确:在 Runnable 内部 try-catch 处理
        empty.ifPresentOrElse(
            System.out::println,
            () -> {
                try {
                    throw new Exception("受检异常");
                } catch (Exception e) {
                    System.err.println("捕获: " + e.getMessage());
                }
            }
        );
    }
}

8.2 or() 返回的 Optional 可能仍是 empty

import java.util.Optional;

public class OrChainPitfall {
    public static void main(String[] args) {
        var result = Optional.<String>empty()
                .or(() -> Optional.empty())  // 仍是 empty
                .or(() -> Optional.empty()); // 仍是 empty

        // or() 链条全部返回 empty 时,最终仍是 Optional.empty
        System.out.println("全部 empty 后: " + result.isEmpty());
        // 不会自动抛异常,必须显式 orElseThrow() 或在末尾 orElse()
        System.out.println("默认值: " + result.orElse("兜底值"));
    }
}

面试考点

问题一:"or() 和 orElseGet() 的区别?"

orElseGet(Supplier<T>) 返回类型 T,是终结操作,拆箱出实际值。or(Supplier<Optional<T>>) 返回 Optional<T>,保持包装状态,支持继续链式调用(如 map、filter、再 or)。多级备选时 or() 更合适。

问题二:"Optional.stream() 的实际用途?"

将 Optional 转成 0 或 1 个元素的 Stream。最大用途是在流管道中的 flatMap(Optional::stream) 一步完成"过滤 empty + 提取值",替代 filter(Optional::isPresent).map(Optional::get) 的组合。

问题三:"orElseThrow() 无参版本和 get() 的区别?"

行为上几乎等价:有值返回值,无值抛异常。但语义不同:get() 是"我假设这里有值"(不安全),orElseThrow() 无参版明确说"没值就是异常"(语义清晰)。JDK 10 的发行说明推荐用 orElseThrow() 替代 get()。


白歌在团队 Wiki 写了三条 Optional 铁律,至今挂在飞翔科技的代码规范页面上:

  1. 不用 isPresent() + get() 组合——用 ifPresentOrElse、orElseGet、orElseThrow
  2. 不用 filter(isPresent).map(get)——用 flatMap(Optional::stream)
  3. 不用 get()——用 orElseThrow()

小崔把这三条打印在了工位上。线上再没出过 Optional 相关的 NPE。

上一页
Stream API 增强(JDK 9)
下一页
String 新增方法(JDK 11)