从 01 开始 从 01 开始
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机相关技巧
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • 最全面的 RSS 教程
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金
  • 职场规划
  • 关于离职
  • 杂谈
  • 自媒体
  • 📖 读书

    • 读书工具
    • 走进科学
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
    • 读书笔记
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档

晓林

程序猿,自由职业者,博主,英语爱好者,健身达人
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机相关技巧
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • 最全面的 RSS 教程
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金
  • 职场规划
  • 关于离职
  • 杂谈
  • 自媒体
  • 📖 读书

    • 读书工具
    • 走进科学
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
    • 读书笔记
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档
  • JavaSE

    • 我的 Java 学习路线
    • 安装 Java
    • Java 数据类型

    • Java 多版本配置
    • 面向对象

    • Java 核心类

    • IO

    • Java 与时间

    • 异常处理

    • 哈希和加密算法

    • Java8 新特性

      • 函数式编程与 Lambda
      • Stream 介绍
      • 创建 Stream
      • 操作 Stream
      • Optional
        • NPE
        • Optional 原理
        • Optional 对象的创建
        • 判断是否为 null
        • 非空则执行表达式
        • 设置与获取默认值
        • filter
        • map
        • filter 和 map 结合
        • 深入 Optional
        • 实践
        • 例 3 简化判空和默认值
        • 总结
        • 参考
    • 网络编程

  • JavaSenior

  • JavaEE

  • JavaWeb

  • Spring

  • 主流框架

  • SpringMVC

  • SpringBoot

  • Java
  • JavaSE
  • Java8 新特性
2023-03-13
目录

Optional

# Optional

本文介绍下 Optional 的概念、基本使用及其带来的好处。

本文主要参考王二哥的文章:Java 8 Optional 最佳指南 (opens new window),结合自己的经验撰写而成

# NPE

在传统开发中,我们经常需要判空(判断是否为 null),不然就经常产生空指针异常,例如:

public class OptionalDemo1NPE {
  public static void main(String[] args) {
    String s = null;
    s.equals("hello");
  }
}
1
2
3
4
5
6

运行结果如下:

$ javac OptionalDemo1NPE.java
$ java OptionalDemo1NPE
Exception in thread "main" java.lang.NullPointerException
        at LearnOptionalDemo1NPE.main(LearnOptionalDemo1NPE.java:4)
1
2
3
4

在工作中,有很多需要判空的场景,例如:

if(null != user){
  Address address = user.getAddress();
  if(null != address){
    Country country = address.getCountry();
    if(null != country){
      //.......
    }
  }
}
1
2
3
4
5
6
7
8
9

一旦代码量大起来,就会有大量的判空代码。

为了解决空指针异常,Google 公司著名的 Guava (opens new window) 项目引入了 Optional 类,Guava 通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到 Google Guava 的启发,Optional 类已经成为 Java 8 类库的一部分

Optional 实际上是个容器:它可以保存类型 T 的值,或者仅仅保存 null。Optional 提供很多有用的方法,这样我们就不用显式进行空值检测。

为此,Java8 引入了 Optional 类。

Optional 实际上是个容器:它可以保存类型 T 的值,或者仅仅保存 null。Optional 提供很多有用的方法,这样我们就不用显式进行空值检测。

# Optional 原理

在继续讲 Optional 的使用之前,先简单说下其原理,免得后续介绍的过程中一头雾水。

Optional,其实就是一个类,可以创建它的对象,可以调用它的一些方法。

Optional 可以帮我们保存一个对象(不管是不是 null),并且自带一个方法判断其是否为 null;如果不是,则返回保存的对象;如果是,则可以返回默认值(非 null),这样就简化了 NPE 的判断。

Optional 可以理解为是一个容器,容器里可能是空的(保存的对象为 null),也可以不是空的

# Optional 对象的创建

Optional 常见的三种创建方式:

  • Optional.empty(T):创建一个空的 Optional 对象,较少使用
  • Optional.of(T):创建一个非空的 Optional 对象,较少使用
  • Opeional.ofNullable(T):创建一个可以为空,也可以不为空的 Optional 对象,较多使用

以上方法都是静态方法。


1)使用静态方法 empty() 创建一个空的 Optional 对象:

Optional<String> empty = Optional.empty();
System.out.println(empty); // 输出:Optional.empty
1
2

2)使用静态方法 of() 创建一个非空的 Optional 对象:传递给 of() 方法的参数不能为 null,否则仍然会抛出 NullPointerException

Optional<String> opt = Optional.of("PeterJXL");
System.out.println(opt); // 输出:Optional[PeterJXL]
1
2

3)使用静态方法 ofNullable() 创建一个即可空又可非空的 Optional 对象

String name = null;
Optional<String> optOrNull = Optional.ofNullable(name);
System.out.println(optOrNull); // 输出:Optional.empty
1
2
3

ofNullable() 方法内部有一个三元表达式,如果为参数为 null,则返回私有常量 EMPTY;否则使用 new 关键字创建了一个新的 Optional 对象——不会再抛出 NPE 异常了。

# 判断是否为 null

创建了 Optional 对象后,就可以判断是否为 null 了,主要有 2 个方法:

  • isPresent():判断其存储的值是否为 null,是则返回 true,否则返回 false(取代了 obj != null 的判断)。Present 的含义是 “存在的”
  • isEmpty():Java11 新增的方法,是 null 则返回 true,否则返回 false(和 isPresent() 输出相反)
Optional<String> opt = Optional.of("PeterJXL");
System.out.println(opt.isPresent());    //true

Optional<String> optOrNull = Optional.ofNullable(null);
System.out.println(optOrNull.isPresent());  //false

//  Java 11新方法 isEmpty
Optional<String> opt2 = Optional.of("PeterJXL");
System.out.println(opt.isEmpty()); // false

Optional<String> optOrNull2 = Optional.ofNullable(null);
System.out.println(optOrNull.isEmpty()); // true
1
2
3
4
5
6
7
8
9
10
11
12

至此,我们就可以用 isPresent() 方法对 Optional 对象进行判空后再执行相应的代码:

Optional<String> optOrNull = Optional.ofNullable(null);
if (optOrNull.isPresent()) {
    System.out.println(optOrNull.get().length());
}
1
2
3
4

注:get 方法是 Optional 对象值的方法,但是如果 Optional 对象的值为 null,会抛出 NoSuchElementException 异常,后续我们会讲其他方法。

使用该种方式,代码量并不见得减少了多少。我们接着往下看

# 非空则执行表达式

上面仅仅是取代了 obj != null 的判断,但我们经常需要判断不为 null 之后,再执行一些语句,因此我们可以用 ifPresent() (注意是 if,而不是 is)。我们可以直接将 Lambda 表达式传递给该方法,代码更加简洁,更加直观:

Optional<String> opt = Optional.of("PeterJXL");
opt.ifPresent(str -> System.out.println(str.length()));
1
2

Java 9 后还可以通过方法 ifPresentOrElse(action, emptyAction) 执行两种结果,非空时执行 action,空时执行 emptyAction。

Optional<String> opt2 = Optional.ofNullable("PeterJXL");
opt2.ifPresentOrElse(str -> System.out.println(str.length()), () -> System.out.println("is Null"));
1
2

# 设置与获取默认值

有时候,我们在创建(获取) Optional 对象的时候,需要一个默认值,orElse(Supplier<T>) 和 orElseGet(Supplier<T>) 方法就派上用场了。

在构造 Optional 对象时传入的 value 值为 null 时,就会调用参数里的函数:

public class OptionalDemo5DefaultValue {
    public static void main(String[] args) {
        String str = null;
        String strOpt = Optional.ofNullable(str).orElse(createString());
        String strOpt2 = Optional.ofNullable(str).orElseGet(() -> createString());
    }

    public static String createString(){
        System.out.println("invoke createString");
        return "zhang san";
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

这两个函数的区别:当 user 值不为 null 时,orElse 函数依然会执行 createUser() 方法,而 orElseGet 函数并不会执行 createUser() 方法,大家可自行测试。

在我们处理的业务数据量大的时候,这两者的性能就有很大的差异。

orElse() 有一个重构的方法,可以直接传入一个对象,参数类型和变量的类型要一致

String nullStr = null;
String name = Optional.ofNullable(nullStr).orElse("PeterJXL");
System.out.println(name);   //PeterJXL
1
2
3

用于返回包裹在 Optional 对象中的值,如果该值不为 null,则返回;否则返回默认值。

orElseGet() 也有另一种写法:传入方法的引用

String strOpt3 = Optional.ofNullable(str).orElseGet(OptionalDemo5DefaultValue::createString);
1

# filter

之前我们仅仅是使用 isPresent 和 ifPresent 方法判断是否为 null,有时候我们判断不为 null 了之后,还需要判断对象是否符合我们的场景:例如,当我们校验密码的时候,除了不能为 null,还对密码的长度、复杂度等进行判断,此时我们可以用 Optional 提供的 filter() 方法

String password = "12345";
Optional<String> opt = Optional.ofNullable(password);
System.out.println(opt.filter(pwd -> pwd.length() > 5).isPresent()); //false
1
2
3

filter() 方法的参数类型为 Predicate(Java 8 新增的一个函数式接口),也就是说可以将一个 Lambda 表达式传递给该方法作为条件,如果表达式的结果为 false,则返回一个 EMPTY 的 Optional 对象,否则返回过滤后的 Optional 对象。

假设密码的长度要求在 6 到 10 位之间,那么还可以再追加一个条件:

Predicate<String> len6 = pwd -> pwd.length() > 6;
Predicate<String> len10 = pwd -> pwd.length() < 10;
password = "1234567";
opt = Optional.ofNullable(password);
boolean result = opt.filter(len6.and(len10)).isPresent();
System.out.println(result); //true
1
2
3
4
5
6

这次程序输出的结果为 true,因为密码变成了 7 位,在 6 到 10 位之间。假如使用 if-else 来判断,代码会很冗长,要有 3 个判断(是否为 null,是否大于 6,是否小于 10)

# map

有时候我们不仅需要判空对象,还需要对象的成员也不为空,也就是我们需要再定义一个 Optional:

String name = "PeterJXL";
Optional<String> nameOptional = Optional.ofNullable(name);
Optional<Integer> integerOptional = Optional.ofNullable(name.length());
1
2
3

此时我们可以用 Optional 的 map 方法,它可以按照一定的规则将原有 Optional 对象转换为一个新的 Optional 对象,原有的 Optional 对象不会更改:

integerOptional = nameOptional.map(String::length);
1

在上面这个例子中,map() 方法的参数 String::length,意味着要 将原有的字符串类型的 Optional 按照字符串长度重新生成一个新的 Optional 对象,类型为 Integer。

# filter 和 map 结合

举个粒子,我们需要判断密码的长度,并且密码不能是简单的密码(例如 password,是弱密码),我们可以结合 Filter 和 Map:

String name = "PeterJXL";
Optional<String> nameOptional = Optional.ofNullable(name);
Optional<Integer> integerOptional = Optional.ofNullable(name.length());
integerOptional = nameOptional.map(String::length);

String password = "Password";
Optional<String> opt = Optional.ofNullable(password);

Predicate<String> len6 = pwd -> pwd.length() > 6;
Predicate<String> len10 = pwd -> pwd.length()  < 10;
Predicate<String> eq = pwd -> pwd.equals("123456");

boolean result = opt.map(String::toLowerCase).filter(len6.and(len10).and(eq)).isPresent();
System.out.println(result); //true
1
2
3
4
5
6
7
8
9
10
11
12
13
14

map() 用于将密码转化为小写,filter() 用于判断长度以及是否是“password”。

# 深入 Optional

# Optional 对象的创建

Optional 的构造函数是 private 权限的,不能由外部调用的。而 Optional.empty(), Optional.of(T value), Optional.ofNullable(T value) 是 public 权限,供我们所调用。

Optional 的本质,就是内部储存了一个真实的值,在构造函数里会调用 Objects 的 requireNonNull 方法:

private Optional(T value) {
    this.value = Objects.requireNonNull(value);
}
1
2
3

而 Objects 里会直接判断其值是否为空:如果为空,则直接抛出异常。

    public static <T> T requireNonNull(T obj) {
        if (obj == null)
            throw new NullPointerException();
        return obj;
    }
1
2
3
4
5

流程图:


我们来逐个讲解创建 Optional 对象的三个方法

首先是 Optional.of(T value),源码如下

public static <T> Optional<T> of(T value) {
    return new Optional<>(value);
}
1
2
3

也就是说 of(T value) 函数内部调用了构造函数。根据构造函数的源码我们可以得出两个结论:

  • 通过 of(T value) 函数所构造出的 Optional 对象,当 Value 值为空时,依然会报 NullPointerException。
  • 通过 of(T value) 函数所构造出的 Optional 对象,当 Value 值不为空时,能正常构造 Optional 对象。

然后是 Optional.empty(),源码如下:

private static final Optional<?> EMPTY = new Optional<>();

private Optional() {
    this.value = null;
}

public static<T> Optional<T> empty() {
    @SuppressWarnings("unchecked")
    Optional<T> t = (Optional<T>) EMPTY;
    return t;
}
1
2
3
4
5
6
7
8
9
10
11

也就是说,Optional 类内部还维护一个 value 为 null 的对象,并创建了一个名为 EMPTY 的 Optional 对象来存储这个对象。而 empty() 的作用就是返回 EMPTY 对象。


最后讲讲我们常用的 ofNullable(T value),源码如下:

 public static <T> Optional<T> ofNullable(T value) {
    return value == null ? empty() : of(value);
}
1
2
3

读者应该都能看得懂什么意思了。相比较 of(T value) 的区别就是:当 value 值为 null 时

  • of(T value) 会报 NullPointerException 异常;
  • ofNullable(T value) 不会 throw Exception,直接返回一个 EMPTY 对象。

因此,我们一般都是用 ofNullable 方法,而不是 of 方法,of 方法的应用场景笔者还没见过,欢迎补充。

# get 方法

public T get() {
    if (value == null) {
        throw new NoSuchElementException("No value present");
    }
    return value;
}
1
2
3
4
5
6

可以看到,如果为空,还是会抛出异常,还不如抛出 NPE 呢 🙄 因此一般很少用 get 方法

# orElseThrow

除了 orElse(Supplier<T>) 和 orElseGet(Supplier<T>),还有一个 orElseThrow 方法,当 value 值为 null 时, 直接抛一个异常出去,用法如下所示

User user = null;
Optional.ofNullable(user).orElseThrow(()->new Exception("用户不存在"));
1
2

如果有判断为 null 后需抛出异常的场景,可以用 orElseThrow 简化

# map 源码

public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent())
        return empty();
    else {
        return Optional.ofNullable(mapper.apply(value));
    }
}
1
2
3
4
5
6
7
8

可以看到 map 其实内部也是创建了一个 Optional 对象来实现转换。

# 实践

# 例 1 简化判空

以前写法

if(user!=null){
    dosomething(user);
}
1
2
3

JAVA8 写法

Optional.ofNullable(user).ifPresent(u->{
   dosomething(u);
});
1
2
3

# 例 2 简化多个判空

在函数方法中,以前写法

public String getCity(User user)  throws Exception{
    if(user!=null){
        if(user.getAddress()!=null){
            Address address = user.getAddress();
            if(address.getCity()!=null){
                return address.getCity();
            }
        }
    }
    throw new Excpetion("取值错误"); 
}
1
2
3
4
5
6
7
8
9
10
11

JAVA8 写法

public String getCity(User user) throws Exception{
    return Optional.ofNullable(user)
                   .map(u-> u.getAddress())
                   .map(a->a.getCity())
                   .orElseThrow(()->new Exception("取指错误"));
}
1
2
3
4
5
6

# 例 3 简化判空和默认值

以前写法

public User getUser(User user) throws Exception{
    if(user != null){
        String name = user.getName();
        if("zhangsan".equals(name)){
            return user;
        }
    }else{
        user = new User();
        user.setName("zhangsan");
        return user;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

java8 写法

public User getUser(User user) {
    return Optional.ofNullable(user)
                   .filter(u->"zhangsan".equals(u.getName()))
                   .orElseGet(()-> {
                        User user1 = new User();
                        user1.setName("zhangsan");
                        return user1;
                   });
}
1
2
3
4
5
6
7
8
9

# 总结

使用了 Optional 后,我们就可以使用链式编程(如同 Stream 流一样),虽然代码优雅了。但是,逻辑性没那么明显,可读性有所降低,在后续编程的时候应酌情考虑是否要使用。

本文所有代码已上传至 GitHub 和 Gitee:

Gitee:src/chapter20Java8 (opens new window)

GitHub:LearnJavaSE/src/chapter20Java8 (opens new window)

# 参考

Java 8 Optional 最佳指南 | Java 程序员进阶之路 (opens new window)

java8 之 Optional 判空,简化判空操作-今日头条 (opens new window)

JAVA8 之妙用 Optional 解决判断 Null 为空的问题_zjhred 的博客-CSDN 博客_optional.ofnull (opens new window)

Java8 新特性之 Optional_雨 ~ 旋律的博客-CSDN 博客_java8 optional (opens new window)

上次更新: 2025/6/3 09:31:54
操作 Stream
TCP 编程

← 操作 Stream TCP 编程→

最近更新
01
语雀文档一键下载至本地教程
07-04
02
要成功,就不要低估环境对你的影响
07-03
03
血泪教训:电子设备要定期开机
07-02
更多文章>
Theme by Vdoing | Copyright © 2022-2025 | 粤 ICP 备 2022067627 号 -1 | 粤公网安备 44011302003646 号 | 点击查看十年之约
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式