为啥需要Optional
null在编程中常常是一个令人头疼的问题,处理不好就会导致NullPointerException(空指针异常)。
为了避免空指针异常,代码中就会充斥着繁琐的null检查和条件嵌套。
Optional的出现帮我们解决了这个问题。它让我们可以更安全、优雅地处理可能为空的值。
问题引入
模拟一个从数据库中根据 userName 查找对应用户的一个例子。
需要一个 User Bean:
java
/**
* @author TheSea
* @description 用户对象
* @createDate 2025/12/27
*/
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
String name;
String age;
}
上面的代码其实还给出了一种非常的编程习惯:
把常量写在 equals 左边是一种防御式编程习惯,可以在参数为 null 的情况下避免 NullPointerException,提升代码健壮性,因此在工程实践中被广泛采用。
如果常量在右边:
java
if (name.equals("TheSea")) {
//.......
}
如果 name == null,则会:
java
Exception in thread "main" java.lang.NullPointerException
原因很简单:
-
equals 是 实例方法
-
null 不能调用任何方法
而常量在左边的时候:
java
if ("TheSea".equals(name)) {
//.......
}
即使 name == null,也 不会报错,原因是:
-
"TheSea" 是一个确定存在的 String 对象
-
String.equals(Object obj) 内部逻辑是安全的
String.equals 源码核心逻辑(简化版):
java
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
// 逐字符比较
}
return false;
}
当 anObject == null 时:
java
null instanceof String // false
直接返回 false,不会抛异常。
或者另外一种写法,也是非常安全的写法,使用 Objects.equals(JDK 7+):
java
if (Objects.equals(name, "TheSea")) {
//......
}
特点:
-
双方都可以是 null
-
可读性强
-
非字符串也适用
内部实现其实就是:
java
(a == b) || (a != null && a.equals(b))
现在还需要一个 Mapper:
java
/**
* @author TheSea
* @description 模拟用户表持久层
* @createDate 2025/12/27
*/
public class UserMapper {
public User getUserByName(String name){
if("TheSea".equals(name)){
return new User("TheSea","25");
}else{
return null;
}
}
}
现在我们可以在 main 里调用:
java
/**
* @author TheSea
* @description 主函数
* @createDate 2025/12/27
*/
public class Main {
public static void main(String[] args) {
UserMapper userMapper = new UserMapper();
User theSea = userMapper.getUserByName("TheSea");
System.out.println(theSea.toString());
}
}
运行结果如下:

此时当我们换一个名字时:
java
public class Main {
public static void main(String[] args) {
UserMapper userMapper = new UserMapper();
User theSea = userMapper.getUserByName("Ocean");
System.out.println(theSea.toString());
}
}
运行结果如下:

为了避免不抛出 NPE,我们会这样写:
java
public class Main {
public static void main(String[] args) {
UserMapper userMapper = new UserMapper();
User theSea = userMapper.getUserByName("Ocean");
if(theSea != null){
System.out.println(theSea.toString());
}
}
}
在实际项目中,我们会处理大量可能为空的值,这样一来代码中就会充斥着大量的条件判断,会让代码显得非常臃肿且难以阅读和维护。
这就是为啥要引入 optional 的原因。
optional 使用
optional 类似于一个盒子,或者说是一个容器,它可以包含某种类型的值,也可以什么都不包含,比如 null。
并且提供一系列方法来操作内部的值。
同时 optional 也考虑了函数式编程的原则,可以和 lambda 表达式或者 stream API 等特性结合使用,可以优雅的进行一些链式调用。
等会儿我们会用 optional 来重新实现一下刚刚的例子。
empty():创建一个包含空值的 optional 对象
java
//empty() 方法会返回一个空的 optional 对象
Optional<Object> optionalBox = Optional.empty();
//使用 optional 内置的方法来检测内部值的状态
//isPresent() 检测 optional 对象内部是否存在值,存在返回 true,否则 false
System.out.println(optionalBox.isPresent()); //输出 false
//注意,创建的是一个空的 optional 对象,所以内部的类型不一定是 object
Optional<String> optionalBox = Optional.empty();
//使用 optional 内置的方法来检测内部值的状态
//isPresent() 检测 optional 对象内部是否存在值,存在返回 true,否则 false
System.out.println(optionalBox.isPresent());
isPresent():检测 optional 对象内部是否有值
java
//empty() 方法会返回一个空的 optional 对象
Optional<Object> optionalBox = Optional.empty();
//使用 optional 内置的方法来检测内部值的状态
//isPresent() 检测 optional 对象内部是否存在值,存在返回 true,否则 false
System.out.println(optionalBox.isPresent()); //输出 false
//注意,创建的是一个空的 optional 对象,所以内部的类型不一定是 object
Optional<String> optionalBox = Optional.empty();
//使用 optional 内置的方法来检测内部值的状态
//isPresent() 检测 optional 对象内部是否存在值,存在返回 true,否则 false
System.out.println(optionalBox.isPresent());
of():创建一个包含普通值的 optional 对象
java
String value = "Ocean";
//将我们的 value 放到 optional 对象中包装起来,也就是放进这个盒子当中
//使用 of() 方法就是返回一个包含字符串对象 "Ocean" 的 optional 对象
Optional<String> optionalBox = Optional.of(value);
//使用 optional 内置的方法来检测内部值的状态
//isPresent() 检测 optional 对象内部是否存在值,存在返回 true,否则 false
System.out.println(optionalBox.isPresent()); //输出 true
注意,用 of() 方法创建的对象必须包含值,所以要确保传递给 of() 方法的值不为 null。
我们来试一下传入值为 null 的场景:
java
String value = null;
//将我们的 value 放到 optional 对象中包装起来,也就是放进这个盒子当中
//使用 of() 方法会返回一个为 null 的 optional 对象
Optional<String> optionalBox = Optional.of(value);
//使用 optional 内置的方法来检测内部值的状态
//isPresent() 检测 optional 对象内部是否存在值,存在返回 true,否则 false
System.out.println(optionalBox.isPresent()); // 抛出NPE
ofNullable():创建一个可包含null值的 optional 对象
如果传入的值要确保不为 null 的话,那这样的话不是还没有解决我们说的总是要判断值为 null 的问题吗?
因此当我们如果不确定传入的值是否为 null 时,应当使用 ofNullable 这个方法来包装值:
java
String value = null;
//将我们的 value 放到 optional 对象中包装起来,也就是放进这个盒子当中
//使用 ofNullable() 方法会返回一个可以将 null 也给视为无值状态的 optional 对象
Optional<String> optionalBox = Optional.ofNullable(value);
//使用 optional 内置的方法来检测内部值的状态
//isPresent() 检测 optional 对象内部是否存在值,存在返回 true,否则 false
System.out.println(optionalBox.isPresent()); //输出 false
传入不为 null 值的测试:
java
String value = "Ocean";
//将我们的 value 放到 optional 对象中包装起来,也就是放进这个盒子当中
//使用 ofNullable() 方法会返回一个可以将 null 也给视为无值状态的 optional 对象
Optional<String> optionalBox = Optional.ofNullable(value);
//使用 optional 内置的方法来检测内部值的状态
//isPresent() 检测 optional 对象内部是否存在值,存在返回 true,否则 false
System.out.println(optionalBox.isPresent()); //输出 true
get():从 optional 中取值
java
String value = "Ocean";
//将我们的 value 放到 optional 对象中包装起来,也就是放进这个盒子当中
//使用 ofNullable() 方法会返回一个可以将 null 也给视为无值状态的 optional 对象
Optional<String> optionalBox = Optional.ofNullable(value);
//使用 get() 从 optional 对象中取出存入的值
String value2 = optionalBox.get();
System.out.println(value2); //输出 Ocean
//使用 optional 内置的方法来检测内部值的状态
//isPresent() 检测 optional 对象内部是否存在值,存在返回 true,否则 false
System.out.println(optionalBox.isPresent()); //输出 true
虽然使用 get 方法来获取 optional 的值非常简单和直观,但是一般不推荐这么做,这违背了 optional 设计的初衷,稍后会讲到。
使用 optional 改写demo
改写如下:
java
public class UserMapper {
public Optional<User> getUserByName(String name){
if("TheSea".equals(name)){
return Optional.of(new User("TheSea","25"));
}else{
return Optional.empty();
}
}
}
java
public class Main {
public static void main(String[] args) {
UserMapper userMapper = new UserMapper();
Optional<User> optioanl = userMapper.getUserByName("TheSea");
User user = optioanl.get();
System.out.println(user);
}
}
运行输出:

当我们使用一个不存在的用户名时:
java
public class Main {
public static void main(String[] args) {
UserMapper userMapper = new UserMapper();
Optional<User> optioanl = userMapper.getUserByName("Ocean");
User user = optioanl.get();
System.out.println(user);
}
}

如果会出现这样的问题,那说明就还是得判断一下是否 optional 内部是否有值才可以,这显然背离了 optional 设计的初衷。
orElse():为空时执行默认操作
因此我们需要使用 orElse() 方法来消除 if 的判断:
java
public class Main {
public static void main(String[] args) {
UserMapper userMapper = new UserMapper();
Optional<User> optional = userMapper.getUserByName("TheSea");
// orElse() 方法在 optional 对象内部有值时返回存储的值,否则返回传入 orElse 内的值
User user = optional.orElse(new User("默认用户名", "默认年龄"));
System.out.println(user);
}
}

当我们传入数据库中不存在的用户名时:
java
public class Main {
public static void main(String[] args) {
UserMapper userMapper = new UserMapper();
Optional<User> optional = userMapper.getUserByName("Ocean");
// orElse() 方法在 optional 对象内部有值时返回存储的值,否则返回传入 orElse 内的值
User user = optional.orElse(new User("默认用户名", "默认年龄"));
System.out.println(user);
}
}

orElseGet():懒加载形式的为空时执行默认操作
与 orElse() 相似的还有一个方法 orElseGet():
java
public class Main {
public static void main(String[] args) {
UserMapper userMapper = new UserMapper();
Optional<User> optional = userMapper.getUserByName("Ocean");
// orElse() 方法在 optional 对象内部有值时返回存储的值,否则返回传入 orElse 内的值
User user = optional.orElse(new User("默认用户名", "默认年龄"));
// orElseGet() 方法在语义上与 orElse 效果相同
// 唯二区别在于:
// 1、如果 optional 对象内部有值,则不会执行获取传入的默认参数(orElse 不管 optional 对象内部有值无值都会 new 默认对象)
// 2、orElseGet 方法传入的是一个 Supplier 对象,可以用 lambda 表达式创建,而 orElse 方法直接传入对象即可
User user1 = optional.orElseGet(() -> new User("默认用户名2", "默认年龄2"));
System.out.println(user);
System.out.println(user1);
}
}
orElseThrow():optional对象内部值为空时抛异常
java
public class Main {
public static void main(String[] args) {
UserMapper userMapper = new UserMapper();
Optional<User> optional = userMapper.getUserByName("TheSea");
// orElseThrow() 方法传入一个 Supplier 参数,可以用 lambda 表达式创建,那么在 optional 对象内部值为空时会抛出对应异常,比如NPE
User user = optional.orElseThrow(() -> new RuntimeException("一个自定义异常"));
System.out.println(user);
}
}

此时如果查的值为 null :
java
public class Main {
public static void main(String[] args) {
UserMapper userMapper = new UserMapper();
Optional<User> optional = userMapper.getUserByName("Ocean");
// orElseThrow() 方法传入一个 Supplier 参数,可以用 lambda 表达式创建,那么在 optional 对象内部值为空时会抛出对应异常,比如NPE
User user = optional.orElseThrow(() -> new RuntimeException("一个自定义异常"));
System.out.println(user);
}
}

ifPresent():值存在时执行的操作
java
public class Main {
public static void main(String[] args) {
UserMapper userMapper = new UserMapper();
Optional<User> optional = userMapper.getUserByName("TheSea");
//ifPresent() 方法接收一个 Consumer 参数,可以用lambda表达式创建,在 optional 对象内部存在值的时候才会执行对应的函数式操作
optional.ifPresent(user -> System.out.println(user));
}
}

如果为空的话 ifPresent 将什么也不做。
filter():根据给定条件过滤 optional 对象中的值
filter() 方法接受一个 Predicate 的函数式接口作为参数 ,该接口用于定义过滤条件。如果值存在且满足条件,filter 会返回一个包含原始值的新的 optional 对象,否则就返回一个空的 optional 对象。
java
public class Main {
public static void main(String[] args) {
UserMapper userMapper = new UserMapper();
Optional<User> optional = userMapper.getUserByName("TheSea");
Optional<User> user1 = optional.filter(user -> Objects.equals(user.getName(), "TheSea"));
//"TheSea" 存在,因此应该打印
user1.ifPresent(user -> System.out.println(user));
}
}

java
public class Main {
public static void main(String[] args) {
UserMapper userMapper = new UserMapper();
Optional<User> optional = userMapper.getUserByName("Ocean");
Optional<User> user1 = optional.filter(user -> Objects.equals(user.getName(), "TheSea"));
//"Ocean" 不存在,因此不执行打印
user1.ifPresent(user -> System.out.println(user));
}
}

由于 filter() 方法返回的是一个 optional 对象,因此我们还可以使用链式编程:
java
public class Main {
public static void main(String[] args) {
UserMapper userMapper = new UserMapper();
Optional<User> optional = userMapper.getUserByName("Ocean");
optional.filter(user -> Objects.equals("TheSea", user.getName()))
.ifPresent(user -> System.out.println(user));
}
}
map():用于对 optional 对象中的值进行转换
map() 方法接收一个 Function 参数,可以用 lambda 参数创建,这个参数会被应用到 optional 对象存储的值上,用于对 optional 对象中存储的值进行转换,并返回一个新的 optional 对象,其中包含了转换后的值。
如果 optional 是空的,那么 map 什么也不做。
java
public class Main {
public static void main(String[] args) {
UserMapper userMapper = new UserMapper();
Optional<User> optional = userMapper.getUserByName("TheSea");
Optional<User> user1 = optional.map(user -> {
if (Objects.equals("TheSea", user.getName())) {
user.setName("TheSea2");
}
return user;
});
System.out.println(user1.orElseThrow(() -> new RuntimeException("TheSea不存在")));
}
}

flatMap():用于扁平化嵌套的 optional 的情况
flatMap() 转换函数返回的必须是另一个 Optional 对象,这就意味着 flatMap() 方法可以用于嵌套的 Optional 的情况。
它会将两个 optional 对象合并成一个。
如果原始的 optional 对象为空,或者转换函数返回的 optional 对象为空,那么最终得到的也是一个空的 Optional 对象。
来举例说明一下。
首先改造一个嵌套的结构,我们将 User 对象的 Age 熟悉用 Optional 包装一下:
java
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class User {
String name;
String age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Optional<String> getAge() {
return Optional.ofNullable(age);
}
public void setAge(String age) {
this.age = age;
}
}
此时我们调用一下:
java
public class Main {
public static void main(String[] args) {
UserMapper userMapper = new UserMapper();
Optional<User> optional = userMapper.getUserByName("TheSea");
//可以看到返回的 age 是一个 optional 的嵌套结构
Optional<Optional<String>> userAge = optional.map(user -> user.getAge());
//如果使用 flatMap 的话,它会合并嵌套的 Optional 对象
Optional<String> userAge2 = optional.flatMap(user -> user.getAge());
}
}
通常情况下,如果只需要对 Optional 对象中的值进行转换,而不需要嵌套的 Optional,那么使用 map() 方法是更合适的选择。但是如果需要进行一些操作,可能会返回另一个 Optional 对象,那么 flatMap 方法更适合。
不过感觉这个用的蛮少的,知道怎么回事就可以。
Optional 推荐使用场景
用于方法返回类型
首先普遍用于方法的返回类型,表示方法可能不返回结果,比如:

当你有一个方法可能返回一个值或者也可能什么都不返回(即返回 null )时就可以使用 optional 包装返回。
比如当我们设计一个 API 的时候,它能引导 API 的使用者告诉他们这个值可能不存在,并强调调用者处理这种结果的可能性:

这样可以大幅度减少错误的发生。
Optional 不推荐使用场景
不应该用于类的字段

由于 Optional 对象的创建和管理有一定的开销,因此并不适用于用作类的字段。
使用 Optional 来作为字段类型会增加内存消耗,并且会使得对象的序列化变得复杂。
不应该用做方法参数
将 Optional 用做方法参数,会导致方法的使用变得难以理解和复杂化:

同理,也不应该用作构造器参数。
不应该用作集合的参数类型

如果你的方法返回一个集合,而且这个集合可能为空,不应该用 optional 来包装它,因为集合已经可以很好地处理空集合的情况了:

不建议使用 Optionl 的 get() 方法
因为调用 Optional 的 get() 方法前,没有确认值是否存在的话会导致 NoSuchElementException。

即使是使用 isPresent() 和 get() 的组合也不是一个最好的选择,这样做和直接调用可能为 null 的引用没多大区别,因为这仍是在进行显式的检查,背离了 Optional 设计的初衷。
而应该转为使用下列方法:

总结







工程上最常用的方法:ofNullable + map + filter + orElseThrow / orElseGet
Optional 的工程级常用方法就是:
ofNullable + map + filter + orElseThrow / orElseGet
其余方法更多是辅助或特定场景使用。




