【Java 新特性系列】Java 8 Optional 类完全指南

Optional 是 Java8 提供的为了解决 null 安全问题的一个 API。善用 Optional 可以使我们代码中很多繁琐、丑陋的设计变得十分优雅。


文章目录

        • 1、Optional 类概述
          • 1.1、Optional 类介绍
          • 1.2、使用 `Optional` 的前后对比
            • 1.2.1、不使用 `Optional`
            • 1.2.2、使用 `Optional`
        • 2、Java 8 中 Optional 类的主要方法
          • 2.1、创建 Optional 对象
            • 2.1.1、`Optional.of(T value)` 方法
            • 2.1.2、`Optional.empty()` 方法
            • 2.1.3、`Optional.ofNullable(T value)` 方法
          • 2.2、条件动作
            • 2.2.1、`isPresent()` 方法
            • 2.2.2、`ifPresent(Consumer<? super T> consumer)` 方法
          • 2.3、值获取
            • 2.3.1、`orElse(T other)` 方法
            • 2.3.2、`get()` 方法
            • 2.3.3、`orElseGet(Supplier<? extends T> supplier)` 方法
            • 2.3.4、`orElseThrow(Supplier<? extends X> exceptionSupplier)` 方法
        • 2.4、转换和过滤
            • 2.4.1、`map(Function<? super T, ? extends U> mapper)` 方法
            • 2.4.2、`flatMap(Function<? super T, Optional<U>> mapper)` 方法
            • 2.4.3、`filter(Predicate<? super T> predicate)` 方法
        • 3、Optional 类使用时的注意事项
          • 3.1、不要在类的字段中使用 `Optional`
          • 3.2、避免使用 `Optional` 作为参数
          • 3.3、不要仅为了避免 `null` 检查而使用 `Optional`
          • 3.4、使用 `Optional` 的链式调用
          • 3.5、避免在 `Optional` 上进行显式的 `null` 检查
          • 3.6、在使用 `get()` 方法之前总是检查是否有值
          • 3.7、利用 `Optional` 提供的方法避免抛出异常
          • 3.8、 理解 `Optional` 和性能的关系
          • 3.9、orElse方法的使用
          • 3.10、避免使用基础类型的 Optional 对象
          • 3.11、Optional的序列化问题


1、Optional 类概述
1.1、Optional 类介绍

Java 8 引入的 Optional 类,是一个可以包含或不包含非空值的容器对象。它的目的是为了提供一种更优雅的方式来处理可能为 null 的值,从而避免直接使用 null 值可能引发的 NullPointerException

使用 Optional 类可以显著改善代码的可读性和健壮性。

Optional 类的主要特点:

  1. 防止 NullPointerException:使用 Optional 可以明确地要求用户在使用变量之前处理 null 情况;
  2. 增强代码可读性:通过使用 Optional 的方法,代码的可读性和意图更加明确;
  3. 集成到 Java 的流 API 中:Optional 类型在 Java 8 的流(Stream)操作中被广泛使用,提供了更复杂的条件查询和变换功能。
1.2、使用 Optional 的前后对比

假设有一个用户信息管理系统,其中用户的 DO 包含多个字段,而前端只需要显示其中的几个字段。我们将从数据库获取用户详细信息,并转换为用户的 VO。

1.2.1、不使用 Optional

不使用 Optional 的情况下,代码可能直接处理可能的 null 值:

public UserVO getUserVOById(Long userId) {UserDO userDO = userMapper.selectById(userId);  // MyBatis 的查询if (userDO == null) {return null;}UserVO userVO = new UserVO();userVO.setName(userDO.getName());userVO.setEmail(userDO.getEmail());// 更多字段赋值...return userVO;
}
1.2.2、使用 Optional

使用 Optional,你可以优雅地处理 null 值,并且代码更具表达性:

public UserVO getUserVOById(Long userId) {Optional<UserDO> userDOOptional = Optional.ofNullable(userMapper.selectById(userId));return userDOOptional.map(userDO -> {UserVO userVO = new UserVO();userVO.setName(userDO.getName());userVO.setEmail(userDO.getEmail());// 更多字段赋值...return userVO;// 如果有值则返回该值,否则返回 null}).orElse(null);
}

在这个例子中,selectById 方法的返回值被包装在一个 Optional 中。这使得可以直接使用 map 方法来转换 UserDOUserVO,而不需要手动检查 null。如果 userDOnullmap 方法不会执行其内部的 lambda 表达式,并且 getUserVOById 方法将返回 orElse 方法中所指定的默认值。


2、Java 8 中 Optional 类的主要方法
2.1、创建 Optional 对象
2.1.1、Optional.of(T value) 方法

Optional.of(T value):创建一个包含非空值的 Optional 对象。如果传入的 valuenull,则会立即抛出 NullPointerException。这个方法用于包装那些确定不为 null 的值。

Optional<String> opt = Optional.of("Hello");
2.1.2、Optional.empty() 方法

Optional.empty():创建一个空的 Optional 实例。这是用来表示没有值的情况,通常用于初始化或在条件分支中返回一个明确的“无值”状态。

Optional<String> opt = Optional.empty();
2.1.3、Optional.ofNullable(T value) 方法

Optional.ofNullable(T value):根据传入的 value 创建一个 Optional 对象。如果 valuenull,则返回一个空的 Optional 实例;如果 valuenull,则创建一个包含该值的 Optional 对象。这个方法用于可能为 null 的情况,提供了一种安全的方式来包装 null 值。

Optional<String> opt = Optional.ofNullable(null);
2.2、条件动作
2.2.1、isPresent() 方法

isPresent():检查 Optional 是否包含值。如果包含值,则返回 true;否则返回 false。这个方法通常在需要基于 Optional 中是否有值来执行不同操作的场合使用。

Optional<String> opt = Optional.of("Hello");
if (opt.isPresent()) {System.out.println("Value is present.");
} else {System.out.println("Value is not present.");
}
2.2.2、ifPresent(Consumer<? super T> consumer) 方法

ifPresent(Consumer<? super T> consumer):如果 Optional 对象包含值,则执行给定的 consumer 操作。这个方法常用于在值存在的情况下执行某些操作,而无需进行显式的空检查。

Optional<String> opt = Optional.of("Hello");
opt.ifPresent(name -> System.out.println("Name is " + name));
2.3、值获取
2.3.1、orElse(T other) 方法

orElse(T other):如果有值则返回该值,否则返回 other。这个方法用于处理 Optional 对象可能为空的情况,提供一个默认值。

String name = Optional.ofNullable(null).orElse("Default Name");
System.out.println(name);  // 输出 "Default Name"
2.3.2、get() 方法

get():如果 Optional 对象包含值,则返回此值;否则抛出 NoSuchElementException。使用 get() 方法前应该先检查是否有值(例如使用 isPresent()),或者使用捕获异常的方式来处理可能的 NoSuchElementException

Optional<String> opt = Optional.of("Hello");
if (opt.isPresent()) {System.out.println(opt.get());  // 输出 "Hello"
} else {System.out.println("No value present");
}
2.3.3、orElseGet(Supplier<? extends T> supplier) 方法

orElseGet(Supplier<? extends T> supplier):如果有值则返回该值,否则执行 supplier 提供的备选的生成策略,返回生成的值。这个方法通常用于延迟计算默认值或获取值的成本较高时。

String name = Optional.ofNullable(null).orElseGet(() -> {// Perform some expensive operation or default value computationreturn "Computed Default Name";
});
System.out.println(name);  // 输出 "Computed Default Name"
2.3.4、orElseThrow(Supplier<? extends X> exceptionSupplier) 方法

orElseThrow(Supplier<? extends X> exceptionSupplier):如果 Optional 对象包含值,则返回此值;如果没有值,抛出由 exceptionSupplier 提供的异常。这使得用户可以定义缺失值时抛出的异常类型。

String result = Optional.ofNullable(null).orElseThrow(() -> new IllegalStateException("Value not present"));
// 将抛出 IllegalStateException

通过以上方法,Optional 提供了灵活的机制来从可能为空的对象中安全地获取值,每种方法适用于不同的应用场景,从而使代码更加健壮且易于维护。

2.4、转换和过滤
2.4.1、map(Function<? super T, ? extends U> mapper) 方法

map(Function<? super T, ? extends U> mapper):如果有值,应用提供的映射函数 mapper,并返回一个 Optional 对象来包含映射函数的结果。如果原 Optional 为空,则仍返回一个空的 Optional

Optional<String> opt = Optional.of("hello");
Optional<String> upper = opt.map(String::toUpperCase);
upper.ifPresent(System.out::println);  // 输出 "HELLO"

通过这些方法,Optional 类为 Java 开发者提供了一种强大的工具来处理可为空的情况,增强了程序的健壮性和可读性。

2.4.2、flatMap(Function<? super T, Optional<U>> mapper) 方法

flatMap(Function<? super T, Optional<U>> mapper):如果 Optional 对象有值,对其值应用提供的 mapper 函数,该函数必须返回 Optional 类型的结果。此方法用于避免嵌套 Optional(即 Optional<Optional<T>>)的情况,使结果保持在单一层级的 Optional

Optional<String> optionalString = Optional.of("hello");
Optional<Integer> length = optionalString.flatMap(s -> Optional.of(s.length()));
length.ifPresent(System.out::println);  // 输出 5
2.4.3、filter(Predicate<? super T> predicate) 方法

filter(Predicate<? super T> predicate):如果 Optional 对象有值,并且该值满足提供的 predicate 条件,则返回包含该值的 Optional;如果不满足条件,则返回一个空的 Optional。这使得 Optional 可以集成更复杂的条件逻辑,提供类似流中的过滤功能。

Optional<String> optionalString = Optional.of("hello");
Optional<String> longString = optionalString.filter(s -> s.length() > 3);
longString.ifPresent(System.out::println);  // 输出 "hello"Optional<String> shortString = optionalString.filter(s -> s.length() < 3);
shortString.ifPresent(System.out::println);  // 不输出任何内容

通过 map, flatMap, 和 filter 方法,Optional 类为 Java 开发者提供了类似于 Java Stream API 的强大的转换和过滤工具,但针对单个可能为空的值。这些方法增强了对 Optional 对象的处理能力,使代码能够以声明性和函数式的方式来处理数据,进一步减少了错误和异常的可能性,特别是与 null 相关的错误。


3、Optional 类使用时的注意事项

使用 Optional 类时,尽管它提供了很多便利,但也有一些注意事项和最佳实践需要遵循,以确保代码的健康性和性能。以下是使用 Optional 时的主要注意事项:

3.1、不要在类的字段中使用 Optional

使用 Optional 作为类的字段通常不推荐,因为 Optional 旨在作为方法的返回类型,用于有效地表示可空的结果。使用它作为字段类型会增加内存开销,同时也违反了其用作临时包装器的设计初衷。

3.2、避免使用 Optional 作为参数

Optional 用作方法参数通常是多余的。这种做法迫使调用者使用 Optional,而不是允许他们以更自然的方式传递值或 null。更好的方法是允许传递 null 并在方法内部使用 Optional.ofNullable() 进行处理。

3.3、不要仅为了避免 null 检查而使用 Optional

Optional 的过度使用可能导致代码质量下降,特别是当它被用来包装几乎从不为 null 的值时。它应该用在真正可能为空的情况,否则会导致不必要的复杂性。

3.4、使用 Optional 的链式调用

利用 Optional 的链式调用,如 mapflatMapfilter 等,可以编写出更简洁、更声明式的代码。这种方式有助于提高代码的可读性和维护性。

3.5、避免在 Optional 上进行显式的 null 检查

Optional 的目的是为了消除代码中的 null 检查。对 Optional 对象本身进行 null 检查是没有必要的,也违背了使用 Optional 的初衷。

3.6、在使用 get() 方法之前总是检查是否有值

直接调用 Optional.get() 而不先检查是否有值,可能会引发 NoSuchElementException。应该使用 isPresent() 或更好的方法是 orElse(), orElseGet(), 或 orElseThrow() 等,以更安全的方式访问值。

3.7、利用 Optional 提供的方法避免抛出异常

在可能的情况下,优先使用 orElse()orElseGet() 而不是 orElseThrow(),除非你确实需要在没有值的情况下抛出一个异常。这样做可以使得程序的控制流更加清晰。

3.8、 理解 Optional 和性能的关系

虽然 Optional 在编码安全性上提供了很多好处,但它也有一定的性能开销,因为每次使用 Optional 都涉及到创建新的对象实例。在性能敏感的应用中,滥用 Optional 可能会导致问题。

总之,Optional 是一个非常有用的工具,能够帮助Java开发者写出更清洁、更健壮的代码。然而,像任何工具一样,它需要在适当的情况下正确使用。理解何时以及如何使用 Optional,可以避免许多常见的编程错误,并最大化其优势。

3.9、orElse方法的使用

orElse 中调用的方法一直都会被执行,orElseGet 方法只有在 Optional 对象不含值时才会被调用,所以使用 orElse 方法时需要谨慎, 以免误执行某些不被预期的操作。此种情况下,可使用 orElseGet 方法代替它。

3.10、避免使用基础类型的 Optional 对象

Optional 提供了的一些基础类型 —— OptionalInt、OptionalLong 以及 OptionalDouble ,但不推荐大家使用基础类型的 Optional,因为基础类型的 Optional 不支持 map、 flatMap 以及 filter 方法,而这些却是 Optional 类常用的方法。可以使用 Optional, Optional, Optional 等替代

3.11、Optional的序列化问题

由于 Optiona l类设计时就没特别考虑将其作为类的字段使用,所以它也并未实现 Serializable 接口。由于这个原因,如果你的应用使用了某些要求序列化的库或者框架,在域模型中使用Optional,有可能引发应用程序故障。

然而,我们相信,通过前面的介绍,我们已经看到用 Optional 声明域模型中的某些类型是个不错的主意,尤其是你需要遍历有可能全部或部分为空,或者可能不存在的对象时。如果你一定要实现序列化的域模型,作为替代方案, 我们建议你像下面这个例子那样,提供一个能访问声明为 Optional、变量值可能缺失的接口,代码清单如下:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/849906.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

信奥之路(五)——顺序结构

** 以顺序、选择和循环三种基本结构的组合来描述程序&#xff0c;是结构化程序设计方法的主要特征之一。每条语句按照自上而下的顺序依次运行一次&#xff0c;这种自上而下依次执行的程序称为顺序结构程序。 ** 1.题目描述 爸爸妈妈让小明去附近的超市买一些玉米回来。爸爸…

单片机原理及技术(三)—— AT89S51单片机(二)(C51编程)

一、AT89S51单片机的并行I/O端口 1.1 P0口 AT89S51的P0口是一个通用的I/O口&#xff0c;可以用于输入和输出。每个引脚都可以通过软件控制为输入或输出模式。 1.1.1 P0口的工作原理 P0口的工作原理是通过对P0寄存器的读写操作来控制P0口的引脚。 输出模式&#xff1a;当P0口…

python --监听鼠标事件

import pyautogui from pynput import mouse, keyboardpyautogui.FAILSAFE Falseclass MouseMonitor:def __init__(self):self.mouse mouse.Controller()self.lock Truedef on_move(self, x, y):鼠标位移时回调函数if self.lock:print(11)pyautogui.moveTo(500, 500)self.loc…

C++ 纯虚函数 virtual = 0

上代码&#xff0c;看一下下面类的封装&#xff1a; class BlockerBase {public:virtual ~BlockerBase() default;virtual void Reset() 0;virtual void ClearObserved() 0;virtual void ClearPublished() 0;virtual void Observe() 0;virtual bool IsObservedEmpty() co…

UI学习笔记(一)

UI学习 一&#xff1a;UIView基础frame属性隐藏视图对象&#xff1a;UIView的层级关系 二&#xff1a;UIWindow对象三&#xff1a;UIViewController基础UIViewController使用 四&#xff1a;定时器与视图移动五&#xff1a;UISwitch控件六&#xff1a;滑动条和进度条七&#xf…

VMware给没安装VMTools的系统封装ISO以送入文件

VMware给没安装VMTools的系统封装ISO以送入文件&#xff0c;其实不需要其它工具 VMware自带mkisofs 2.01&#xff0c;不过mkisofs 2.01已经停止更新&#xff0c;最大的问题是不支持中文&#xff0c;也不支持UEFI引导记录&#xff0c;但一般已经够用了&#xff0c;除此之外还可…

跨库数据同步 SYNC data

1.exp imp 由于DBV行不通 2. 直接使用pl/sql 的导入导出&#xff0c;导出insert脚本肯定不现实&#xff0c;导成专用pde文件&#xff0c;发现24小时只能导入1000多万表&#xff08;基本每两三分钟10000&#xff09;。 3.使用expdp impdp, 遇到的问题&#xff08;imperva会拦截…

2021年vue面试题整理(万字解析)

一、对MVVM的理解 MVVM分为Model、View、ViewModel。 Model 代表数据模型&#xff0c;数据和业务逻辑都在Model层中定义&#xff1b;泛指后端进行的各种业务逻辑处理和数据操控&#xff0c;对于前端来说就是后端提供的 api 接口。 View 代表UI视图&#xff0c;负责数据的展示…

默认launcher

目录 前提代码 前提 刷机后开机提示选择launcher应用&#xff0c;此时设备中有至少两个apk配置有属性&#xff0c;想要开机自动进入launcher,可以通过修改ResolverActivity,在开机时默认选择指定的launcher程序 代码 //frameworks/base/core/java/com/android/internal/app/…

【Python数据分析--Numpy库】Python数据分析Numpy库学习笔记,Python数据分析教程,Python数据分析学习笔记(小白入门)

一&#xff0c;Numpy教程 给大家推荐一个很不错的笔记&#xff0c;个人长期学习过程中整理的 Python超详细的学习笔记共21W字点我获取 1-1 安装 1-1-1 使用已有的发行版本 对于许多用户&#xff0c;尤其是在 Windows 上&#xff0c;最简单的方法是下载以下的 Python 发行版…

java 8 新特性CompletableFuture使用

准备工作&#xff1a;定义一个线程池 ExecutorService pool Executors.newFixedThreadPool(3,(Runnable r)->{Thread tnew Thread(r);t.setDaemon(true);return t;});一、执行方式 1、对于有返回值的 CompletableFuture<String> futureCompletableFuture.supplyAsync…

Swift 序列(Sequence)排序面面俱到 - 从过去到现在(一)

概览 在任何语言中对序列(或集合)元素的排序无疑是一种司空见惯的常规操作,在 Swift 语言里自然也不例外。序列排序看似简单,实则“暗藏玄机”。 要想真正掌握 Swift 语言中对排序的“各种姿势”,我们还得从长计议。不如就先从最简单的排序基本功开始聊起吧。 在本篇博…

【十大排序算法】插入排序

插入排序&#xff0c;如一位细心的整理者&#xff0c; 她从序列的左端开始&#xff0c; 挨个将元素归位。 每当她遇到一个无序的元素&#xff0c; 便将它插入已经有序的部分&#xff0c; 直至所有元素有序排列。 她不张扬&#xff0c;却有效率&#xff0c; 用自己的方式&…

探索HTML5新Input类型:重塑表单交互的未来

随着HTML5标准的演进&#xff0c;表单设计迎来了重大革新&#xff0c;其中最引人注目的莫过于一系列新的input类型。这些新类型不仅简化了前端开发&#xff0c;提升了用户体验&#xff0c;还增强了网页表单的数据验证能力。然而&#xff0c;值得注意的是&#xff0c;不同浏览器…

什么是umi

UMI&#xff08;Umi Next.js Inspired Middleware&#xff09;是一个基于 React 的企业级前端应用框架&#xff0c;由阿里巴巴团队开发和维护。UMI 框架结合了 React、Webpack、Babel、Dva&#xff08;一个基于 Redux 和 redux-saga 的数据流方案&#xff09;等主流前端技术&am…

pdf文件在线压缩网站,pdf文件在线压缩工具软件

在数字化时代的今天&#xff0c;PDF文件已经成为我们日常生活和工作中不可或缺的一部分。然而&#xff0c;随着PDF文件的广泛使用&#xff0c;其文件大小问题也日益凸显。过大的PDF文件不仅占用了大量的存储空间&#xff0c;而且在传输和共享过程中也往往面临诸多不便。因此&am…

SylixOS网卡多 IP 配置

概述 网卡多 IP 是指在同一个网络接口上配置和绑定多个 IP 地址。 引进网卡多 IP 的目的主要有以下几个&#xff1a; 提供服务高可用性。通过在同一接口绑定多个 IP 地址&#xff0c;然后在服务端使用这些 IP 地址启动多个服务实例。这样在任意一 IP 出现问题时&#xff0c;可…

Redis学习(十二)Redis的三种删除策略

目录 一、背景二、Redis 的三种删除策略2.1 定时删除&#xff08;用CPU换内存空间&#xff09;2.2 定期删除2.3 惰性删除&#xff08;用内存换CPU性能&#xff09; 三、总结 一、背景 我们都知道 Redis 是一种内存数据&#xff0c;所有的数据均存储在内存中&#xff0c;可以通…

Android 代码打印meminfo

旨在替代adb shell dumpsys meminfo packageName&#xff0c;在log打印meminfo&#xff0c;以便分析内存情况 ActivityManager.MemoryInfo memoryInfo new ActivityManager.MemoryInfo(); activityManager.getMemoryInfo(memoryInfo); long totalMemory Runtime.getRuntime(…

大数据环境搭建@Hive编译

Hive3.1.3编译 1.编译原因1.1Guava依赖冲突1.2开启MetaStore后运行有StatsTask报错1.3Spark版本过低 2.环境部署2.1jdk安装2.2maven部署2.3安装图形化桌面2.4安装Git2.5安装IDEA 3.拉取Hive源码4.Hive源码编译4.1环境测试1.测试方法——编译2.问题及解决方案&#x1f4a5;问题1…