[Java SE] 核心源码精讲:java.util.Optional(JDK1.8-)
0 场景引入
引入背景
- 本质上,
Optional
是一个包含有可选值的包装类,这意味着Optional
类既可以含有对象
也可以为空(null/empty)
。 Optional
是Java
实现函数式编程的强劲一步,并且帮助在范式
中实现。但是Optional
的意义显然不止于此。- 在
Java 8
之前,任何访问对象方法或属性的调用都可能导致NullPointerException
:
我们从一个简单的用例开始:
String isocode = user.getAddress().getCountry().getIsocode().toUpperCase();
在这个小示例中,如果我们需要确保不触发异常,就得在访问每一个值之前对其进行明确地检查:
if (user != null) {
Address address = user.getAddress();
if (address != null) {
Country country = address.getCountry();
if (country != null) {
String isocode = country.getIsocode();
if (isocode != null) {
isocode = isocode.toUpperCase();
}
}
}
}
案例源码
你看到了,这很容易就变得冗长,难以维护。
为了简化这个过程,我们来看看用 Optional
类是怎么做的。从创建和验证实例,到使用其不同的方法,并与其它返回相同类型的方法相结合,下面是见证Optional
奇迹的时刻。
WithoutOptionalTest
package test.java.util;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.Test;
/**
* @create-time 2022/12/29 11:00
* @description 没有 Optional 会有什么问题?
* 我们来模拟一个实际的应用场景。
* 小王第一天上班,领导老马就给他安排了一个任务,要他从数据库中根据会员 ID 拉取一个会员的姓名,然后将姓名打印到控制台。
* 虽然是新来的,但这个任务难不倒小王,于是他花了 10 分钟写下了本段代码(WithoutOptionalTest)
* @reference-doc
* [1] 干货,一文彻底搞懂 Java 的 Optional - CSDN - https://blog.csdn.net/qing_gee/article/details/104767082
*/
@Slf4j
public class WithoutOptionalTest {
/**
* 由于当前 ID 的会员不存在,故 getMemberByIdFromDB() 返回了 null 来作为没有获取到该会员的结果。
* 意味着:在打印会员姓名的时候要先对 mem 判空;否则,就会抛出 NPE 异常。
* 不信?让小王把 if (mem != null) 去掉试试,控制台立马打印错误堆栈给你颜色看看。
*/
@Test
public void withoutOptionalTest(){
Member mem = getMemberByIdFromDB();
if (mem != null) {
log.info("member.name: {}", mem.getName());
}
Assert.assertTrue(true);
}
public static Member getMemberByIdFromDB() {
// DB查询结果:当前 ID 的会员不存在
return null;
}
class Member {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
OptionalTest
package test.java.util;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.Test;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
/**
* @create-time 2022/12/29 10:59
* @description Optional 是如何解决这个问题的?
* 小王把代码提交后,就兴高采烈地去找老马要新的任务了。
* 本着虚心学习的态度,小王请求老马看一下自己的代码,于是老王就告诉他应该尝试一下 Optional,可以避免没有必要的 null 值检查。
* 现在,让我们来看看小王是如何通过 Optional 来解决上述问题的。
* @reference-doc
* [1] 干货,一文彻底搞懂 Java 的 Optional - CSDN - https://blog.csdn.net/qing_gee/article/details/104767082
*/
@Slf4j
public class OptionalTest {
/**
* getMemberByIdFromDB() 方法返回了 Optional<Member> 作为结果,这样就表明 Member 可能存在,也可能不存在,这时候就可以:
* 在 Optional 的 ifPresent() 方法中使用 Lambda 表达式来直接打印结果。
*
* Optional 之所以可以解决 NPE 的问题,是因为:它明确的告诉我们,不需要对它进行判空。它就好像十字路口的路标,明确地告诉你该往哪走。
*/
@Test
public void withOptionalTest(){
Optional<Member> optional = getMemberByIdFromDB();
optional.ifPresent(mem -> {
log.debug("member.name: {}", mem.getName());
});
log.debug("optional.isPresent : {}", optional.isPresent());
log.debug("optional.get() : {}", optional.get());
optional = Optional.empty();
Member member = optional.orElse(new Member("optional 为空时设置的默认会员信息"));
log.debug("member : {}", member);
//如果存在该值,返回包含的值,否则抛出由 Supplier 继承的异常
//optional.orElseThrow(() -> { return new RuntimeException("optional为空时出现异常!"); });
Assert.assertTrue(true);
}
/**
* ofNullable() 方法内部有一个三元表达式:
* 如果为参数为 null,则返回私有常量 EMPTY;
* 否则,使用 new 关键字创建了一个新的 Optional 对象——不会再抛出 NPE 异常了。
*/
@Test
public void ofNullableTest(){
//true
List<Member> list = Collections.EMPTY_LIST;
Optional<List<Member>> optionalList = Optional.ofNullable(list);
boolean present = optionalList.isPresent();
log.debug("present : {}", present);
Assert.assertTrue(true);
}
@Test
public void createOptionalObject01(){
//可以使用静态方法 empty() 创建一个空的 Optional 对象
Optional<String> empty = Optional.empty();
log.debug(empty.toString()); // 输出:Optional.empty
String str = null;
Optional<String> optionalStr = Optional.ofNullable(str);
boolean isPresent2 = optionalStr.isPresent();
log.debug("optionalStr : {}", optionalStr); // 输出:Optional.empty
log.debug("isPresent2 : {}", isPresent2); // 输出:false
Assert.assertTrue(true);
}
@Test
public void createOptionalObject02(){
//可以使用静态方法 of() 创建一个非空的 Optional 对象
Optional<String> opt = Optional.of("沉默王二");
log.debug(opt.toString()); // 输出:Optional[沉默王二]
//当然了,传递给 of() 方法的参数必须是非空的,也就是说不能为 null,否则仍然会抛出 NullPointerException。
// String name = null;
// Optional<String> optnull = Optional.of(name);// java.lang.NullPointerException
Assert.assertTrue(true);
}
@Test
public void createOptionalObject03(){
String name = null;
Optional<String> optOrNull = Optional.ofNullable(name);
log.debug("optOrNull : {}", optOrNull); // 输出:Optional.empty
Assert.assertTrue(true);
}
@Test
public void OrElseTest(){
// 有时候,我们在创建(获取) Optional 对象的时候,需要一个默认值,orElse() 和 orElseGet() 方法就派上用场了。
// orElse() 方法用于返回包裹在 Optional 对象中的值,如果该值不为 null,则返回 原本的值;否则返回默认值。该方法的参数类型和值得类型一致。
String nullName = null;
String name = Optional.ofNullable(nullName).orElse("沉默王二");
log.debug("name : {}", name); // 输出:沉默王二
String nullName2 = "hello";
String name2 = Optional.ofNullable(nullName2).orElse("沉默王二");
log.debug("name2 : {}", name2); // 输出:hello
Assert.assertTrue(true);
}
@Test
public void orElseGetTest(){
//orElseGet() 方法与 orElse() 方法类似,但参数类型不同。如果 Optional 对象中的值为 null,则执行参数中的函数。
String nullName = null;
String name = Optional.ofNullable(nullName).orElseGet(()-> { return "沉默王二"; });
log.debug("name : {}", name); // 输出:沉默王二
Assert.assertTrue(true);
}
public static Optional<Member> getMemberByIdFromDB() {
boolean hasName = true;
if (hasName) {
return Optional.of(new Member("沉默王二"));
}
return Optional.empty();
}
}
class Member {
private String name;
public Member(String name) {
this.name = name;
}
public String getName() {
return name;
}
// getter / setter
@Override
public String toString() {
return "Member{" +
"name='" + name + '\'' +
'}';
}
}
1 概述: java.util.Optional
该类是
Java 8
中引入的一个用于处理可能为null
的值的容器类。
-
这是一个容器对象,可能包含或不包含非空值。
-
自 Java 1.8 起支持
-
如果有值存在,
isPresent()
方法将返回 true;而get()
方法将返回该值,若无值存在,将报异常java.util.NoSuchElementException: No value present
。 -
提供了一些依赖于包含值的存在或缺失的其他方法。例如
orElse()
(如果值不存在,则返回默认值)和ifPresent()
(如果存在值,则执行一段代码块)。 -
这是一个基于值的类;在
Optional
实例上使用依赖于身份的操作(包括引用相等性(==
)、身份哈希码或同步)可能会产生不可预测的结果,应该避免使用。 -
它的设计目的是为了解决在代码中频繁出现的空指针异常问题。
不过这个类有好有坏,适当使用即可,其缺点就是造成代码过于冗长(= - = )
最主要是引入了额外的对象开销。
2 API
对象创建
创建
Optional
的对下你个有以下三种方式:
Optional.of(value)
: 创建一个包含非空值的 Optional 对象
如果传递的值为 null
,会抛出 NullPointerException
Optional<String> nonNullOptional = Optional.of("Hello, World!");
Optional.ofNullable(value)
: 创建一个 Optional
包装对象,如果传递的值为 null
,仍然会创建一个空的 Optional
对象
Optional.ofNullable
是Java
中java.util.Optional
类的一个静态方法
public static <T> Optional<T> ofNullable(T value)
- 其用于返回一个指定值的
Optional
对象。 - 这个方法的主要作用是将一个可能为
null
的值包装到Optional
容器中。 - 如果该值为
null
,则返回一个空的 Optional 对象;否则,返回一个包含该值的Optional
对象。
Optional<String> optional = Optional.ofNullable(someNullableValue);
Optional.empty()
: 创建一个空的 Optional
对象
Optional<String> emptyOptional = Optional.empty();
操作方法
isPresent()
: 判断 Optional 对象是否包含非空值
if (optional.isPresent()) {
// 做一些操作
}
ifPresent(Consumer<? super T> consumer)
: 如果 Optional 对象包含非空值,则执行传入的操作
optional.ifPresent(value -> System.out.println("Value: " + value));
orElse(T other)
: 如果 Optional
对象包含非空值,则返回该值;否则返回传入的默认值
String result = optional.orElse("Default Value");
orElseGet(Supplier<? extends T> other)
: 类似于 orElse,但是默认值是由传入的 Supplier
提供的
String result = optional.orElseGet( () -> generateDefaultValue() );
orElseThrow(Supplier<? extends X> exceptionSupplier)
: 如果 Optional
对象为空,则抛出由传入的 Supplier
提供的异常
String result = optional.orElseThrow(() -> new NoSuchElementException("Value not present"));
链式操作
- 链式操作:Optional 提供了一些方法,在不解包 Optional 的情况下进行链式操作(这有助于避免空指针异常,并使代码更加清晰)
比如如下例子: nullableValue 不为 null,则将其转换为大写;否则,返回默认值 "Default"
String result = Optional.ofNullable(nullableValue)
.map(String::toUpperCase)
.orElse("Default");
//if String nullableValue = null , then : result = "Default"
//if String nullableValue = "Hello" , then : result = "HELLO"
3 应用场景
应用场景一般用于方法返回值或者集合操作中
场景1:当方法的返回值可能为 null 时,使用 Optional
可以清晰地表达这一点,并要求调用者明确处理可能的空值情况
public Optional<String> findUserById(String userId) {
// 在数据库中查找用户,可能返回 null
// 使用 Optional 封装返回值
// 返回 Optional.ofNullable(user) 或 Optional.empty()
}
场景2:在对集合进行操作时,某些操作可能返回 null(例如在查找元素时)。使用 Optional 可以更好地处理这些情况,而不是直接返回 null
public Optional<String> findFirstEvenNumber(List<Integer> numbers) {
// 在集合中查找第一个偶数,可能返回 null
// 使用 Optional 封装返回值
// 返回 Optional.ofNullable(evenNumber) 或 Optional.empty()
}
场景3:在防御性编程中,通过使用 Optional 可以更好地处理可能为 null 的值,避免空指针异常
public Optional<String> getUserName(User user) {
// 获取用户的名称,用户对象可能为 null
// 使用 Optional 封装返回值
// 返回 Optional.ofNullable(userName) 或 Optional.empty()
}
场景4:设计自己的 API 时,如果某个方法的返回值可能为空,使用 Optional 可以使 API 更加健壮和易于理解。
public Optional<Product> findProductById(String productId) {
// 根据产品ID查找产品,产品可能不存在
// 使用 Optional 封装返回值
// 返回 Optional.ofNullable(product) 或 Optional.empty()
}
场景5:当处理多层嵌套的对象时,使用 Optional 可以避免深层次的 null 检查,使代码更加简洁
public Optional<String> getAddressCity(User user) {
// 获取用户地址的城市,用户、地址对象可能为 null
// 使用 Optional 封装返回值
// 返回 Optional.ofNullable(city) 或 Optional.empty()
}
4 注意事项
虽然看起来很新鲜, 可以提高代码的健壮性,但需要注意不要滥用。主要如下,还是重点讲讲:
- 造成代码冗长,本来一个
null
值就可以解决 - 多一个对象多一个开销,如果本身追求速度,就不用刻意这样
- 如果嵌套多层对象还这么使用,开发的可能感觉很牛,但Review的同事可能觉得很臭
X 参考文献

本文链接: https://www.cnblogs.com/johnnyzen
关于博文:评论和私信会在第一时间回复,或直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
日常交流:大数据与软件开发-QQ交流群: 774386015 【入群二维码】参见左下角。您的支持、鼓励是博主技术写作的重要动力!