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

    • 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 详解

周五下午,飞翔科技的代码评审会上,小崔的一段代码引起了争议。

Employee emp = employeeDao.findById(1001);
String dept = emp.getDepartment().getName();

大翔皱了皱眉:"小崔,如果员工不存在呢?getDepartment() 返回 null 呢?"

小崔自信地说:"我前面判断了 if (emp != null) 啊——啊等等,我把那个 PR 里的 null 检查给删了..."

白歌叹气:"这就是 NullPointerException 的由来。Tony Hoare 称 null 是他'十亿美元的错误'。"

朱璐好奇:"那 JDK 8 的 Optional 真的能消除 NPE 吗?"

大翔笑道:"Optional 不是万能药,但用好了能让代码健壮性提升一个档次。今天就讲讲 Optional 的正确打开方式。"


一、定义表

方法分类描述返回值
Optional.empty()创建创建一个空的 OptionalOptional<T>
Optional.of(T value)创建包装非 null 值,value 为 null 则抛 NPEOptional<T>
Optional.ofNullable(T value)创建包装值,value 可为 null(自动变为 empty)Optional<T>
get()取值获取值,若为空抛 NoSuchElementExceptionT
isPresent()判断是否存在值boolean
ifPresent(Consumer<T>)消费值存在时执行操作void
orElse(T other)默认值存在返回值,否则返回 otherT
orElseGet(Supplier<T>)惰性默认值存在返回值,否则执行 Supplier 获取T
orElseThrow(Supplier<X>)异常处理存在返回值,否则抛Supplier产生的异常T
map(Function<T, R>)转换值存在时转换,返回 Optional<R>Optional<R>
flatMap(Function<T, Optional<R>>)扁平转换值存在时转换,避免嵌套 OptionalOptional<R>
filter(Predicate<T>)过滤值满足条件时保留,否则变 emptyOptional<T>

二、Mermaid 流程图:Optional 值处理管道


三、Mermaid 流程图:Optional 方法选择决策树


四、创建 Optional 的正确方式

4.1 三种创建方法对比

// 1. of —— 明确知道非 null
Optional<String> opt1 = Optional.of("飞翔科技"); // OK
// Optional<String> opt2 = Optional.of(null);     // ← NullPointerException!

// 2. ofNullable —— 值可能为 null(最常用)
Optional<String> opt3 = Optional.ofNullable(null);  // 返回 Optional.empty
Optional<String> opt4 = Optional.ofNullable("大翔"); // 返回 Optional[大翔]

// 3. empty —— 明确返回空
Optional<String> opt5 = Optional.empty();  // 返回 Optional.empty

4.2 最佳实践:API 返回值设计

// ✓ 正确:DAO 方法返回 Optional 明确表达"可能没有结果"
public Optional<Employee> findById(int id) {
    Employee emp = database.get(id);
    return Optional.ofNullable(emp);
}

// ✗ 错误:不要返回 null Optional
public Optional<Employee> findById(int id) {
    if (id <= 0) return null;  // ← 绝对不要!
    return Optional.ofNullable(database.get(id));
}

五、orElse 与 orElseGet 的致命区别

这是 Optional 最容易踩的坑!看代码:

// 假设有一个昂贵的操作
public String computeExpensiveDefault() {
    System.out.println("执行了昂贵的默认值计算...");
    return "默认值";
}

Optional&lt;String&gt; opt = Optional.of("飞翔科技");

// orElse:无论 Optional 是否有值,都会执行参数表达式!
String result1 = opt.orElse(computeExpensiveDefault());
// 输出:执行了昂贵的默认值计算...  ← 即使 opt 有值也执行了!

// orElseGet:只有 Optional 为空时才执行
String result2 = opt.orElseGet(() -> computeExpensiveDefault());
// 无输出 ← opt 有值,Supplier 不会执行

记忆法则:

  • orElse(T) —— eager 求值,参数在调用前已计算完毕
  • orElseGet(Supplier&lt;T&gt;) —— lazy 求值,仅在需要时执行

六、map 与 flatMap 的图文对比

class Employee {
    private Department department;
    public Optional&lt;Department&gt; getDepartment() {  // 返回 Optional
        return Optional.ofNullable(department);
    }
}

class Department {
    private String name;
    public String getName() { return name; }
}

// 场景:获取员工所在部门名称
Optional&lt;Employee&gt; optEmp = Optional.ofNullable(findEmployee(1001));

// map:直接转换 → 返回 Optional&lt;String&gt;
Optional&lt;String&gt; deptName = optEmp
    .map(Employee::getDepartment)     // Optional<Optional&lt;Department&gt;> ← 双重包装!
    .map(dept -> dept.getName());     // 编译错误!dept 是 Optional&lt;Department&gt; 不是 Department

// flatMap:扁平化转换 → 返回 Optional&lt;String&gt;
Optional&lt;String&gt; deptName = optEmp
    .flatMap(Employee::getDepartment)  // Optional&lt;Department&gt; ← 扁平化!
    .map(Department::getName);         // Optional&lt;String&gt; ← 正常工作

七、完整代码示例

示例1:飞翔科技员工查询系统防 NPE

import java.util.*;
import java.util.function.Supplier;

/**
 * 飞翔科技员工查询系统 —— Optional 实战
 * 演示如何用 Optional 优雅处理多层嵌套的 null 安全问题
 */

// --- 实体类(模拟复杂嵌套结构)---
class Employee {
    private Integer id;
    private String name;
    private Department department;   // 可能为 null
    private Office office;           // 可能为 null
    private Double salary;

    public Employee(Integer id, String name, Department department, Office office, Double salary) {
        this.id = id; this.name = name; this.department = department;
        this.office = office; this.salary = salary;
    }

    public Integer getId() { return id; }
    public String getName() { return name; }
    // 返回 Optional 明确表达可能为 null
    public Optional&lt;Department&gt; getDepartment() { return Optional.ofNullable(department); }
    public Optional&lt;Office&gt; getOffice() { return Optional.ofNullable(office); }
    public Optional&lt;Double&gt; getSalary() { return Optional.ofNullable(salary); }

    @Override
    public String toString() {
        return String.format("员工[%d, %s, 薪资¥%s]",
                id, name, salary != null ? String.format("%.2f", salary) : "未知");
    }
}

class Department {
    private String deptName;
    private String leader;  // 部门负责人,可能为 null

    public Department(String deptName, String leader) {
        this.deptName = deptName; this.leader = leader;
    }
    public String getDeptName() { return deptName; }
    public Optional&lt;String&gt; getLeader() { return Optional.ofNullable(leader); }
}

class Office {
    private String building;
    private Integer floor;
    private String city;

    public Office(String building, Integer floor, String city) {
        this.building = building; this.floor = floor; this.city = city;
    }
    public String getBuilding() { return building; }
    public Integer getFloor() { return floor; }
    public String getCity() { return city; }
    public String getFullAddress() {
        return city + "市 " + building + " " + floor + "层";
    }
}

// --- 模拟数据库 ---
class EmployeeRepository {
    private static Map&lt;Integer, Employee&gt; DB = new LinkedHashMap<>();

    static {
        DB.put(1001, new Employee(1001, "大翔",
                new Department("研发部", "Frank"),
                new Office("飞翔大厦A座", 15, "北京"), 18888.88));
        DB.put(1002, new Employee(1002, "白歌",
                new Department("研发部", null),  // 部门负责人为空
                new Office("飞翔大厦A座", 12, "深圳"), 15888.88));
        DB.put(1003, new Employee(1003, "小崔",
                new Department("研发部", "Frank"),
                null,  // 办公室信息为空
                8888.88));
        DB.put(1004, new Employee(1004, "朱璐", null, null, null)); // 几乎全空
        // ID 1005 不存在
    }

    public Optional&lt;Employee&gt; findById(int id) {
        return Optional.ofNullable(DB.get(id));
    }

    public List&lt;Employee&gt; findAll() {
        return new ArrayList<>(DB.values());
    }
}

public class OptionalDemo {
    public static void main(String[] args) {
        System.out.println("========== 飞翔科技员工查询系统 ==========\n");

        EmployeeRepository repo = new EmployeeRepository();

        // ===== 场景1:基本查询 + orElse =====
        System.out.println("【场景1:基本查询 orElse / orElseThrow】");
        Employee emp1 = repo.findById(1001)
                .orElseThrow(() -> new RuntimeException("员工不存在!"));
        System.out.println("  找到: " + emp1);

        Employee defaultEmp = repo.findById(9999)
                .orElse(new Employee(-1, "默认员工", null, null, 0.0));
        System.out.println("  未找到ID 9999,返回默认: " + defaultEmp);

        // ===== 场景2:map 链式访问多层嵌套 =====
        System.out.println("\n【场景2:map 安全访问嵌套属性】");

        // 传统方式(易NPE):
        // String leader = emp.getDepartment().getLeader(); ← 任一环节为null就炸

        // Optional 方式:获取大翔的部门负责人
        String leader1001 = repo.findById(1001)
                .flatMap(Employee::getDepartment)
                .flatMap(Department::getLeader)
                .orElse("未指定负责人");
        System.out.println("  大翔的部门负责人: " + leader1001);

        // 白歌的部门负责人为 null
        String leader1002 = repo.findById(1002)
                .flatMap(Employee::getDepartment)
                .flatMap(Department::getLeader)
                .orElse("未指定负责人");
        System.out.println("  白歌的部门负责人: " + leader1002);

        // 朱璐没有部门
        String leader1004 = repo.findById(1004)
                .flatMap(Employee::getDepartment)
                .flatMap(Department::getLeader)
                .orElse("未指定负责人");
        System.out.println("  朱璐的部门负责人: " + leader1004);

        // ===== 场景3:filter 条件过滤 =====
        System.out.println("\n【场景3:filter 条件过滤 + ifPresent】");
        repo.findById(1001)
                .filter(e -> e.getSalary().orElse(0.0) > 15000)
                .ifPresent(e -> System.out.println("  ✓ " + e.getName() + " 薪资 > 15000,符合晋升条件"));
        repo.findById(1003)
                .filter(e -> e.getSalary().orElse(0.0) > 15000)
                .ifPresent(e -> System.out.println("  ✓ 符合晋升条件"));
        // 小崔薪资8888 < 15000,filter后变empty,ifPresent不执行

        // ===== 场景4:orElseGet 惰性求值 =====
        System.out.println("\n【场景4:orElse vs orElseGet】");
        String val1 = repo.findById(1001)
                .map(Employee::getName)
                .orElse(expensiveFallback());
        System.out.println("  orElse 结果: " + val1 + "(注意:expensiveFallback 还是执行了!)\n");

        String val2 = repo.findById(1001)
                .map(Employee::getName)
                .orElseGet(() -> expensiveFallback());
        System.out.println("  orElseGet 结果: " + val2 + "(expensiveFallback 没有执行 ✓)");

        // ===== 场景5:ifPresent 安全消费 =====
        System.out.println("\n【场景5:ifPresent 安全消费】");
        System.out.print("  员工 1001 的办公室: ");
        repo.findById(1001)
                .flatMap(Employee::getOffice)
                .map(Office::getFullAddress)
                .ifPresent(System.out::println);

        System.out.print("  员工 1003 的办公室: ");
        repo.findById(1003)
                .flatMap(Employee::getOffice)
                .map(Office::getFullAddress)
                .ifPresentOrElse(
                    System.out::println,
                    () -> System.out.println("无办公室信息") // JDK 9+ 才有 ifPresentOrElse
                );
        // JDK 8 替代方案:
        Optional&lt;String&gt; office1003 = repo.findById(1003)
                .flatMap(Employee::getOffice)
                .map(Office::getFullAddress);
        if (office1003.isPresent()) {
            System.out.println(office1003.get());
        } else {
            System.out.println("无办公室信息");
        }

        // ===== 场景6:Stream + Optional 联动 =====
        System.out.println("\n【场景6:Stream + Optional 联动】");
        List&lt;Employee&gt; allEmps = repo.findAll();
        System.out.println("  所有员工薪资汇总:");
        double totalSalary = allEmps.stream()
                .map(Employee::getSalary)
                .filter(Optional::isPresent)
                .mapToDouble(Optional::get)
                .sum();
        System.out.printf("  总薪资池: ¥%.2f\n", totalSalary);

        // 更优雅写法
        double totalSalary2 = allEmps.stream()
                .flatMap(e -> e.getSalary()
                        .map(Stream::of)
                        .orElseGet(Stream::empty))
                .mapToDouble(Double::doubleValue)
                .sum();
        System.out.printf("  总薪资池(flatMap): ¥%.2f\n", totalSalary2);

        System.out.println("\n========== End ==========");
    }

    private static String expensiveFallback() {
        System.out.println("  [expensiveFallback] 执行了昂贵的查询...");
        return "Fallback-Value";
    }
}

运行输出:

========== 飞翔科技员工查询系统 ==========

【场景1:基本查询 orElse / orElseThrow】
  找到: 员工[1001, 大翔, 薪资¥18888.88]
  未找到ID 9999,返回默认: 员工[-1, 默认员工, 薪资¥0.00]

【场景2:map 安全访问嵌套属性】
  大翔的部门负责人: Frank
  白歌的部门负责人: 未指定负责人
  朱璐的部门负责人: 未指定负责人

【场景3:filter 条件过滤 + ifPresent】
  ✓ 大翔 薪资 > 15000,符合晋升条件

【场景4:orElse vs orElseGet】
  [expensiveFallback] 执行了昂贵的查询...
  orElse 结果: 大翔(注意:expensiveFallback 还是执行了!)

  orElseGet 结果: 大翔(expensiveFallback 没有执行 ✓)

【场景5:ifPresent 安全消费】
  员工 1001 的办公室: 北京市 飞翔大厦A座 15层
  员工 1003 的办公室: 无办公室信息

【场景6:Stream + Optional 联动】
  所有员工薪资汇总:
  总薪资池: ¥46666.64
  总薪资池(flatMap): ¥46666.64

========== End ==========

示例2:飞翔科技配置读取安全处理

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 飞翔科技应用配置管理器 —— Optional 防 NPE 实战
 */

// 模拟配置层级:系统配置 → 用户配置 → 默认配置
class AppConfig {
    // 模拟配置文件
    private static Map<String, Map&lt;String, String&gt;> config = new LinkedHashMap<>();

    static {
        Map&lt;String, String&gt; dbConfig = new LinkedHashMap<>();
        dbConfig.put("host", "192.168.1.100");
        dbConfig.put("port", "3306");
        dbConfig.put("user", "feixiang_admin");
        // 故意不设置 password

        Map&lt;String, String&gt; appConfig = new LinkedHashMap<>();
        appConfig.put("app.name", "飞翔科技OA系统");
        appConfig.put("app.version", "3.2.1");
        appConfig.put("app.maxUploadSize", "50");
        // 故意不设置 app.timeout

        Map&lt;String, String&gt; featureFlags = new LinkedHashMap<>();
        featureFlags.put("feature.nightMode", "true");
        featureFlags.put("feature.aiAssistant", "false");
        featureFlags.put("feature.darkTheme", null); // null 值

        config.put("database", dbConfig);
        config.put("application", appConfig);
        config.put("features", featureFlags);
    }

    /**
     * 安全获取配置值
     */
    public static Optional&lt;String&gt; getConfig(String group, String key) {
        return Optional.ofNullable(config.get(group))
                .map(m -> m.get(key));
    }

    /**
     * 获取整型配置
     */
    public static Optional&lt;Integer&gt; getIntConfig(String group, String key) {
        return getConfig(group, key)
                .flatMap(val -> {
                    try {
                        return Optional.of(Integer.parseInt(val));
                    } catch (NumberFormatException e) {
                        return Optional.empty();
                    }
                });
    }

    /**
     * 获取布尔配置
     */
    public static boolean getBooleanConfig(String group, String key, boolean defaultValue) {
        return getConfig(group, key)
                .map(String::toLowerCase)
                .map(val -> val.equals("true") || val.equals("1"))
                .orElse(defaultValue);
    }
}

public class ConfigDemo {
    public static void main(String[] args) {
        System.out.println("========== 飞翔科技配置系统 ==========\n");

        // ===== 1. 基本配置读取 =====
        System.out.println("【1> 数据库配置】");
        System.out.println("  host: " + AppConfig.getConfig("database", "host").orElse("localhost"));
        System.out.println("  port: " + AppConfig.getIntConfig("database", "port").orElse(3306));
        System.out.println("  user: " + AppConfig.getConfig("database", "user").orElse("root"));

        // 不存在的配置
        System.out.println("  password: " + AppConfig.getConfig("database", "password")
                .orElseGet(() -> {
                    System.out.println("    ⚠ 数据库密码未配置,使用默认密码");
                    return "feixiang_default_pwd";
                }));

        // ===== 2. 配置不存在时的优雅降级 =====
        System.out.println("\n【2> 应用配置 - 降级处理】");
        String name = AppConfig.getConfig("application", "app.name")
                .orElse("未命名应用");
        System.out.println("  应用名: " + name);

        int maxUpload = AppConfig.getIntConfig("application", "app.maxUploadSize")
                .filter(size -> size > 0 && size <= 500)
                .orElse(10); // 超出范围或未配则默认10MB
        System.out.println("  最大上传: " + maxUpload + " MB");

        // timeout 未配置 → 使用多层降级
        int timeout = AppConfig.getIntConfig("application", "app.timeout")
                .orElse(30); // 未配置则默认30秒
        System.out.println("  超时时间: " + timeout + " s (使用默认值)");

        // ===== 3. 特性开关 =====
        System.out.println("\n【3> 特性开关】");
        System.out.println("  夜间模式: " +
                AppConfig.getBooleanConfig("features", "feature.nightMode", false));
        System.out.println("  AI助手: " +
                AppConfig.getBooleanConfig("features", "feature.aiAssistant", true));
        // darkTheme 值为 null → 走默认值
        System.out.println("  暗黑主题: " +
                AppConfig.getBooleanConfig("features", "feature.darkTheme", false));

        // ===== 4. 验证必需配置 =====
        System.out.println("\n【4> 必需配置验证】");
        try {
            String dbHost = AppConfig.getConfig("database", "host")
                    .filter(h -> !h.isEmpty())
                    .orElseThrow(() -> new IllegalStateException(
                            "致命错误:数据库主机地址未配置!"));
            System.out.println("  ✓ 数据库主机已配置: " + dbHost);
        } catch (IllegalStateException e) {
            System.out.println("  ✗ " + e.getMessage());
        }

        // 验证不存在的必需配置
        try {
            AppConfig.getConfig("database", "sslCert")
                    .orElseThrow(() -> new IllegalStateException(
                            "致命错误:SSL证书路径未配置!"));
        } catch (IllegalStateException e) {
            System.out.println("  ✗ " + e.getMessage());
        }

        // ===== 5. 演示传统代码 vs Optional 代码(同一逻辑) =====
        System.out.println("\n【5> 传统 null 检查 vs Optional 链式调用】");

        String group = "database";
        String key = "host";

        // 传统方式:嵌套 if-else
        String traditionalResult = null;
        Map&lt;String, String&gt; groupConfig = AppConfig.config.get(group);
        if (groupConfig != null) {
            String value = groupConfig.get(key);
            if (value != null) {
                traditionalResult = value;
            } else {
                traditionalResult = "localhost";
            }
        } else {
            traditionalResult = "localhost";
        }
        System.out.println("  传统方式结果: " + traditionalResult);

        // Optional 方式
        String optionalResult = Optional.ofNullable(AppConfig.config.get(group))
                .map(m -> m.get(key))
                .orElse("localhost");
        System.out.println("  Optional方式: " + optionalResult);

        System.out.println("\n========== 配置加载完成 ==========");
    }
}

运行输出:

========== 飞翔科技配置系统 ==========

【1> 数据库配置】
  host: 192.168.1.100
  port: 3306
  user: feixiang_admin
    ⚠ 数据库密码未配置,使用默认密码
  password: feixiang_default_pwd

【2> 应用配置 - 降级处理】
  应用名: 飞翔科技OA系统
  最大上传: 50 MB
  超时时间: 30 s (使用默认值)

【3> 特性开关】
  夜间模式: true
  AI助手: false
  暗黑主题: false

【4> 必需配置验证】
  ✓ 数据库主机已配置: 192.168.1.100
  ✗ 致命错误:SSL证书路径未配置!

【5> 传统 null 检查 vs Optional 链式调用】
  传统方式结果: 192.168.1.100
  Optional方式: 192.168.1.100

========== 配置加载完成 ==========

八、易错场景

易错1:将 Optional 用作字段类型

// ✗ 错误:不要这样做!
public class Employee {
    private Optional&lt;String&gt; name;       // ← 不要用 Optional 做字段!
    private Optional&lt;Department&gt; dept;   // ← 不要!
}

// ✓ 正确:字段还是普通引用,getter 可以返回 Optional
public class Employee {
    private String name;
    private Department department;

    public Optional&lt;String&gt; getName() { return Optional.ofNullable(name); }
    public Optional&lt;Department&gt; getDepartment() { return Optional.ofNullable(department); }
}

// 原因:
// 1. Optional 不是 Serializable(虽然可以实现,但不是设计目标)
// 2. Optional 不应作为字段会增加内存开销
// 3. 违反了 Optional 的设计初衷——它应该作为返回值类型

易错2:将 Optional 用作方法参数

// ✗ 错误:不要这样做!
public void processEmployee(Optional&lt;Employee&gt; empOpt) {
    empOpt.ifPresent(emp -> { /* ... */ });
}
// 调用方被迫做:processEmployee(Optional.ofNullable(emp))

// ✓ 正确:参数用原始类型,必要时在方法内部包装
public void processEmployee(Employee emp) {
    Optional.ofNullable(emp).ifPresent(e -> { /* ... */ });
}

易错3:在集合中使用 Optional

// ✗ 错误:不要将 Optional 放入集合
List<Optional&lt;String&gt;> list = new ArrayList<>();  // ← 错误
Map<String, Optional&lt;Integer&gt;> map = new HashMap<>(); // ← 错误

// ✓ 正确:使用空集合、null 或专门的空对象
List<String> list = new ArrayList<>();  // 空 List 表示无值
Map<String, Integer> map = new HashMap<>(); // 不含 key 即无值

易错4:对空 Optional 调用 get() 而不检查

// ✗ 危险:
Optional&lt;String&gt; opt = Optional.empty();
String val = opt.get();  // 会抛出 NoSuchElementException!

// ✓ 安全:
String val = opt.orElse("默认值");

// ✓ JDK 10+ 替代:opt.orElseThrow() 能更清晰表达意图
// 但在 JDK 8 中只能用 opt.orElseThrow(SomeException::new)

易错5:orElse 中执行副作用操作

// ✗ 错误:orElse 中的代码总是执行,无论 Optional 是否为空
Optional.of("大翔").orElse(logAndReturn("不存在"));  // logAndReturn 必执行!

// ✓ 正确:
Optional.of("大翔").orElseGet(() -> logAndReturn("不存在"));  // 仅在空时执行

九、面试考点

考点1:Optional 的设计初衷是什么?它解决了什么问题?

答: Optional 是 JDK 8 引入的一个容器类,代表一个值可能存在也可能不存在。其核心目的是:

  1. 强制 API 使用者考虑 null 情况:返回 Optional 的方法明确告诉调用者"结果可能为空",强迫程序员显式处理。
  2. 消除 NPE:通过链式 API(map/flatMap/filter/orElse)安全地处理嵌套 null。
  3. 函数式编程风格:避免深层嵌套的 if-else 判空代码,使代码更加声明式。

但 Optional 不是万能药——它不能也不应该完全替代 null,而是用于特定场景(主要是方法的返回值)。

考点2:orElse 和 orElseGet 有什么区别?如何选择?

答:

orElse(T)orElseGet(Supplier<T>)
参数类型值函数式接口
求值时机Eager(立即求值)Lazy(惰性求值)
性能默认值总是被构造仅在需要时构造
适用场景默认值是常量或已存在的对象默认值需要计算、查询数据库、耗时操作
副作用参数表达式总是执行仅在 Optional 为空时执行
// 简单常量 → orElse
.orElse("默认值")

// 需要计算 → orElseGet
.orElseGet(() -> db.queryFallback())  // 仅在空时才查库

考点3:Optional 的 map 和 flatMap 有什么区别?

答:

  • map(Function&lt;T, R&gt;):对 Optional 内的值进行普通转换。如果函数返回 Optional,会导致 Optional<Optional<R>> 的双重包装。
  • flatMap(Function<T, Optional<R>>):对 Optional 内的值进行扁平化转换。函数的返回值本身就是 Optional,flatMap 会自动"解包",避免双重包装。
// map 场景:简单类型转换
Optional&lt;String&gt; upper = optName.map(String::toUpperCase);

// flatMap 场景:链式访问可能为 null 的属性
Optional&lt;String&gt; leader = optEmp
    .flatMap(Employee::getDepartment)  // getDepartment() 返回 Optional&lt;Department&gt;
    .flatMap(Department::getLeader);   // getLeader() 返回 Optional&lt;String&gt;

考点4:Optional 有哪些使用上的反模式(Anti-Pattern)?

答:

  1. 不要用作字段:Optional 设计为返回值容器,不应作为类的成员变量。
  2. 不要用作方法参数:迫使调用方包装参数,增加不必要的复杂度。
  3. 不要用在集合中:如 List<Optional&lt;T&gt;>——直接用空 List 或 filter 可达到同样效果。
  4. 不要对 Optional 做 null 检查:返回 null Optional 违背了 Optional 的本意——调用方会再次面对 NPE。应返回 Optional.empty()。
  5. 不要调用 get() 而不检查:get() 是"危险"操作,应始终配合 isPresent() 或直接用 orElse/orElseGet/orElseThrow。
  6. 不要对集合用 Optional.ofNullable() 包装:空集合本身就可以表达"无数据",应直接返回空集合而非 Optional<List&lt;T&gt;>。

大翔合上笔记本总结:"Optional 的核心哲学就一句话——让 API 的返回类型自己说话。你看到 Optional&lt;Employee&gt;,就知道这个结果可能为空,必须处理。"

白歌补充:"记住三个原则:1) Optional 只做返回值,不做字段和参数;2) 默认值计算用 orElseGet 而不是 orElse;3) 嵌套属性访问用 flatMap 而不是 map。"

小崔突然醒悟:"原来我之前写的 if (obj != null) { if (obj.getXxx() != null) { ... } } 就是 Optional 要消灭的!"

朱璐笑道:"对,这就是传说中的'金字塔厄运'(Pyramid of Doom)。"

黄俪(HR)在门口喊道:"你们能不能把员工档案查询系统的 NPE 都修一下?上周 Production 又炸了!"

众人异口同声:"用 Optional!"


JDK 8 Optional 限制(JDK 9/10/11 已解决,详见第17章「Optional 增强」)

JDK 8 的 Optional 有以下几个限制,在后续版本中陆续解决:

  • 没有 ifPresentOrElse() → JDK 9 加入(值存在/不存在双分支处理)
  • 没有 or() → JDK 9 加入(链式备选 Optional,保持管道不中断)
  • 没有 stream() → JDK 9 加入(Optional → Stream,配合 flatMap 优雅过滤空值)
  • orElseThrow() 必须传异常 Supplier → JDK 10 支持无参(默认 NoSuchElementException)
  • 没有 isEmpty() → JDK 11 加入(比 !isPresent() 更自然、更可读)

在 JDK 8 中,这些需求需要通过 isPresent() + get() 或额外工具方法来实现。升级到 JDK 11 后,建议优先使用上述增强方法。

上一页
Stream API 高级详解
下一页
新日期时间API详解