前言
到目前为止,臭名昭著的 NullPointerException 是导致 Java 应用程序失败的最常见原因。以前,为了解决空指针异常,Google公司著名的Guava项目引入了 Optional 类,Guava 通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到 Google Guava 的启发,Optional 类已经成为 Java 类库的一部分。
Optional 类是一个包含了NULL值或者非NULL值的对象容器,它常用作明确表明没有结果(其实明确表明存在结果也可以用Optional表示)的方法返回类型,这样可以避免 NullPointerException 带来的可能的异常。Optional 被定义为一个简单的容器,其值可能是null或者不是null。在 Java8 之前一般某个函数应该返回非空对象,但偶尔还是有可能返回了 null,而在 Java8 中,不推荐你返回null而是返回 Optional。简而言之,Optional 类只是对类进行简单封装,没啥特殊的。本文会逐个探讨 Optional 类包含的方法,并通过一两个示例展示如何使用。
一、Optional 概述
1.1 什么是 Optional?
在我们的开发中,NullPointerException 可谓是随时随处可见,为了避免空指针异常,我们常常需要进行一些防御式的检查,所以在代码中常常可见if(obj != null) 这样的判断。实际项目中会处理大量为空的值,代码会有很多的条件判断,难以阅读与维护。我们从一个简单的用例开始,在 Java 8 之前,任何访问对象方法或属性的调用都可能导致 NullPointerException。如果我们需要确保不触发异常,就得在访问每一个值之前对其进行明确地检查:
java
if(user!=null){
System.out.println(user.getFullName());
} else {
User defaultUser = new User("Stark", "Tony Stark");
System.out.println(defaultUser.getFullName());
}
幸好在 JDK1.8 中,Java 为我们提供了一个 Optional 类。Opitonal 是 Java8 引入的一个新类,目的是为了解决空指针异常问题,强制在可能为空的情况下进行显式的处理,以避免空指针异常。本质上,这是一个包含有可选值的包装类,这意味着 Optional 类既可以含有对象也可以为空。Optional 类提供了一系列方法来方便地操作内部的值。常用的方法有get、orElse、orElseGet、orElseThrow等。Optional 的设计也考虑了函数式编程的原则,可以与 Lambda 表达式和 Stream API 等特性结合使用,可以进行链式调用替代命令式编程的方式通过编写if条件语句检查null值。Optional 是 Java 实现函数式编程的强劲一步,并且帮助在范式中实现,但是 Optional 的意义显然不止于此。
1.2 为什么需要 Optional?
- 避免 NullPointerException:Optional 类通过提供一种包装机制来解决可能为空的值的问题。它可以将一个值包装在Optional对象中,并通过一些方法提供了安全地访问、操作和处理这个值的方式。使用 Optional 明确表示某个值可能为空,从而迫使开发者在处理值之前考虑为空的情况,避免了直接调用 null 值导致的异常。
- 提升代码可读性:Optional 提供了一种语义化更好的方式来表示"值可能为空",通过调用 Optional 对象的方法,可以安全地访问包含的值。相比直接返回 null,返回一个 Optional 能清楚地告诉调用方这个值是可选的。Optional 类提供了一些方法来判断Optional对象是否包含非空值,通过调用Optional对象的方法,可以处理值不存在时的情况。
- 提高代码健壮性:通过 Optional 的 API,如 orElse()、ifPresent()、map() 等,可以以更优雅的方式处理值的存在与否,减少 if-else 的复杂逻辑。通过使用 Optional,可以避免大量的空指针检查和异常处理代码,使得程序更加简洁和安全。
二、Optional 的使用
2.1 构造方法
Optional 是一个容器,容器里面包着一个值,这个值是泛型。因为构造方法私有化,所以不能通过 new 新建 Optional
对象,只能通过静态工厂方法构造对象。
java
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
2.2 创建Optional实例
Optional 类的设计是基于函数式编程的思想,其实现了 java.util.function 包中的 Supplier、Consumer、Predicate、Function 等接口,这使得它可以和 lambda 表达式或者方法引用一起使用,形成更简洁和优雅的代码。Optional 类被 final 修饰,因此它是一个不可变的类,它有几个静态方法用于创建 Optional 对象,如下表所示:
方法 | 说明 |
---|---|
Optional.empty() | 创建一个空的 Optional 对象 |
Optional.of(T value) | 创建一个值不为 null 的Optional 对象,参数值不为空,否则会抛出 NPE 异常 |
Optional.ofNullable(T value) | 构造一个可能为空,也可能不为空的 optional 对象 |
Optional 类似容器,可以包含各种类型的值,也可以为null。若创建的对象为null,则可以采用 empty() 方法,使用 Optional.empty()
方法来创建一个空的Optional实例。例如:
java
@Test
public void emptyTest() {
// 创建一个空的 Optional 对象
Optional<String> empty = Optional.empty();
try {
System.out.println(empty.get());
} catch (NoSuchElementException exp) {
System.out.println("抛出 NoSuchElementException 异常");
}
}
毫不奇怪,尝试访问空 Optional 的值会导致 NoSuchElementException,鉴于此,我们可以使用 of() 和 ofNullable() 方法创建包含值的 Optional。如果明确一个对象实例不为 NULL 的时候,应该使用 Optional.of()
,例如:
java
@Test
public void ofTest() {
// 创建一个非空的 Optional 对象
Optional<String> hello = Optional.of("独泪了无痕");
try {
Optional.of(null);
} catch (NullPointerException exp) {
System.out.println("抛出 NullPointerException 异常");
}
}
如果用 of 方法创建 Optional 对象时,所传入的值为 null,则抛出异常。因此,应该明确对象不为 null 的时候使用 of()。如果无法明确一个对象实例是否为 NULL 的时候,就应该使用 ofNullable() 方法。ofNullable() 方法接受一个对象作为参数,如果该对象为 null,它会返回一个空的 Optional 对象。否则,它会将对象包装在 Optional 容器中。
java
@Test
public void ofNullableTest() {
// 创建一个可能为空的 Optional 对象,不管所传入的值是否为null,创建的时候都不会报错
Optional.ofNullable(null);
Optional<String> name = Optional.ofNullable("Hello");
}
三、Optional 对象的使用
Optional 对象提供了一些方法,让我们可以更方便地处理可能为空的值,而不需要显式地进行空值检查或者使用 null,以下是一些常用的方法。
3.1 判断值
从 Optional 实例中取回实际值对象的方法之一是使用 get() 方法,不过,从上面示例中就可以看到,这个方法会在值为 null 的时候抛出异常。若是要避免异常,可以选择首先验证是否有值,如下所示:
方法 | 返回值 | 作用 |
---|---|---|
isEmpty() | boolean | 当前对象为 null 返回 true,否则返回 false。 |
isPresent() | boolean | 用于判断非空,相当于 != null。如果值存在,返回 true,否则返回 false。 |
ifPresent(Consumer action) | void | 如果创建的 Optional 中的值存在,则执行该方法的调用,否则什么也不做 |
ifPresentOrElse(Consumer action, Runnable emptyAction) | void | isPresent() 的加强版,值存在时执行第一个操作,否则执行另一个操作。 |
java
/**
* isPresent方法。判断包装对象是否为空
*/
System.out.println(Optional.of("独泪了无痕").isPresent());
System.out.println(Optional.empty().isPresent());
/**
* ifPresent方法。不为空时进行操作,内部自动判断null处理
*/
Optional.of("独泪了无痕").ifPresent(System.out::println);
Optional.empty().ifPresent(System.out::println);
3.2 获取值
在我们的开发中,NullPointerException 可谓是随时随处可见,为了避免空指针异常,我们常常需要进行一些防御式的检查,所以在代码中常常可见if(obj != null) 这样的判断。幸好 Java Optional 类能让我们省掉繁琐的非空的判断,下面先说一下Optional中为我们提供的方法。
方法 | 返回值 | 作用 |
---|---|---|
T get | T | 获取 Optional 中的值。如果值为空,则抛出 NoSuchElementException。 |
T orElse(T other) | T | 如果有值则将其返回,否则返回指定的other对象。 |
T orElseGet(Supplier supplier) | T | 如果有值则将其返回,否则返回由 Supplier 接口实现提供的对象。 |
T orElseThrow(Supplier exceptionSupplier) | void | 如果有值则将其返回,否则抛出由 Supplier 接口实现提供的异常。 |
从 Optional 实例中取回实际值对象的方法之一是使用 get() 方法,如果 Optional 对象不包含值,get() 方法将抛出 NoSuchElementException 异常。要避免异常,就可以使用 orElse() 方法提供一个默认值,以便在 Optional 对象不包含值时返回。如果创建的 Optional 中有值存在,则返回此值,否则返回传递给它的参数值。
java
@Test
public void orElseTest() {
System.out.println(Optional.empty().orElse("默认值"));
System.out.println(Optional.of("Optional对象中的值").orElse("默认值"));
}
在上面的代码中,由于 Optional 实例中不包含值,所以返回了一个默认值。orElseGet 与 orElse 有所不同,这个方法会创建的Optional中有值存在,有值的时候返回值,如果没有值,它会执行作为参数传入的 Supplier(供应者) 函数式接口,并将返回其执行结果。也就是说,其返回的结果是由我们所创建的。
java
@Test
public void orElseGetTest() {
System.out.println(Optional.empty().orElseGet(() -> "默认值"));
System.out.println(Optional.of("orElseGet").orElseGet(() -> "默认值"));
}
下面我们来看一个示例,orElse、orElseGet二者的区别。
java
@Test
public void orElseAndOrElseGetTest() {
@Data
@AllArgsConstructor
@NoArgsConstructor
class User {
private String name;
public User createUser(String str) {
System.out.println(str + " 创建的对象--");
return new User(str);
}
}
User user = new User("独泪了无痕");
System.out.println(Optional.of(user).orElse(user.createUser("orElse")));
System.out.println(Optional.of(user).orElseGet(() -> user.createUser("orElseGet")));
}
可以看到,这个示例中,两个 Optional 对象都包含非空值,两个方法都会返回对应的非空值。不过,orElse() 方法仍然创建了 User 对象。与之相反,orElseGet() 方法不创建 User 对象。在执行较密集的调用时,比如调用 Web 服务或数据查询,这个差异会对性能产生重大影响。
除了 orElse() 和 orElseGet() 方法,Optional 还定义了 orElseThrow()。如果创建的 Optional 中有值存在,则返回此值,否则抛出一个由指定的 Supplier 接口生成的异常,而不是返回备选的值。
java
@Test
public void orElseThrowTest() {
@Data
@NoArgsConstructor
class User {
private String name;
}
User user = null;
Optional.ofNullable(user).orElseThrow(IllegalArgumentException::new);
}
这里,如果 user 值为 null,会抛出 IllegalArgumentException。 这个方法让我们有更丰富的语义,可以决定抛出什么样的异常,而不总是抛出 NullPointerException。
3.3 过滤值
方法 | 简要说明 |
---|---|
Optional filter(Predicate predicate) | 通过传入限定条件对 Optional 实例的值进行过滤。 如果有值并且满足条件,返回包含该值的 Optional,否则返回空的 Optional。 |
filter() 接受一个 Predicate 参数,用于对 Optional 对象中的值进行过滤。如果创建的 Optional 中的值满足 filter 中的条件,则返回包含该值的 Optional 对象,否则返回一个空的 Optional 对象。
java
@Test
public void filterTest() {
System.out.println(Optional.of("zhangsan").filter(e -> e.length() > 5).orElse("王五"));
Optional<String> emptyOptional = Optional.empty();
System.out.println(emptyOptional.filter(e -> e.length() > 5).orElse("lisi"));
}
3.4 转换值
Optional 类型还提供了很多方便的方法来对 Optional 对象进行链式调用,有很多种方法可以转换 Optional 的值,我们从 map()、flatMap() 方法开始,可以帮助我们更方便地对 Optional 实例中的值进行操作。
方法 | 简要说明 |
---|---|
Optional map(Function mapper) | 如果有值,则对其执行调用 mapper 函数对 Optional 中的值进行转换,返回新的 Optional 对象。 若 optional 的值为空,则 map 方法什么也不会做,直接返回空的 Optional 对象。 |
Optional flatMap(Function mapper) | 如果值存在,返回基于Optional包含的映射方法的值,否则返回空Optional |
map() 方法用于将 Optional 对象中的值通过指定的映射函数转换成另外一种类型,先来看一个使用 map() 的例子:
java
@Test
public void mapTest() {
Optional<String> stringOptional = Optional.of("独泪了无痕");
System.out.println(stringOptional.map(String::toUpperCase).orElse("失败"));
stringOptional = Optional.empty();
System.out.println(stringOptional.map(String::toUpperCase).orElse("失败"));
}
flatmap 方法用于扁平化嵌套的 Optional 结构,以避免引入不必要的嵌套层级,具体为 flatmap 的转换函数返回的必须是另一个 Optional 对象,意味着 flatMap 方法可以用于嵌套的 Optional 情况,可以将两个为嵌套关系的 Optional 对象转换为一个。如果原始的 Optional 对象为空,或转换函数返回的 Optional 对象为空,那么最终得到的也是为空的 Optional 对象。
java
@Test
public void flatMapTest() {
Optional<String> optional = Optional.of("独泪了无痕");
optional.flatMap(str -> Optional.of(str).map(String::length)).ifPresent(System.out::println);
}
在这个例子中,flatMap() 方法将字符串的长度封装在一个新的 Optional 中,并返回这个长度。简单来说,map、flatMap 二者不同,若只需要对 Optional 对象中的值进行转换,而不需要嵌套的 Optional,那么使用 map 方法更合适。如果要进行一些操作返回另外一个 Optional 对象,flatmap 方法更合适。
四、小结
Optional 是 Java 8 中为解决 null 引发问题的一大进步,它通过提供一套清晰的 API,让开发者显式地处理值的存在与否,避免了空指针异常,提升了代码的健壮性和可读性。使用 Optional,不仅可以简化代码,还能让代码更加语义化,是现代 Java 编程中不可或缺的工具。有一点要注意,Optional 是无法被序列化,所以不要试图将 Optional 作为方法参数进行定义,也不要在类当中声明 Optional 类型的成员变量。Optional 通常只作为方法的返回值,用来规避空指针异常。在使用 Optional 时,应该使用函数式的编程风格。
需要注意的是,在使用Optional类时,最好避免过度使用链式操作,以保持代码的可读性和可维护性。同时,建议在需要处理可能为空的值时,使用Optional来替代传统的空指针检查和处理机制。总的来说,这个简单而强大的类有助于创建简单、可读性更强、比对应程序错误更少的程序。