一、概述
Optional 是 Java 8 引入的新特性,它是一种特殊的包装类,里面只存储一个元素(这一点与基本数据类型的包装类有点相似)。有的文档称其为容器类,但它不同于 Conllection框架中的集合类,它一个容器只能存储一个元素。
Optional是Java中的一个类,它的作用是用于解决空指针异常的问题,它提供了一些有用的方法,可以帮助我们避免显式进行空值检测。
Java中的Optional类,可以包装类型T的值,这是一个可以为null的容器对象。Java 8 引入Optional替代以前版本中的空值(null),可以避免空指针异常(NullPointerException)问题,表示一个值可能存在也可能不存在。
Optional 可表示一个非空或空的Optional对象。如果一个Optional实例是非空的,表示它保存有一个类型为T的值;如果Optional实例为空(null),则类型T的值不存在。
Optional提供很多有用的方法,以避免显式地进行空值检测。
如果值存在,那么方法isPresent()会返回true,调用get()方法会返回该对象;
如果值不存在,那么方法isPresent()会返回false,调用get()方法会产生空指针异常(NullPointerException, 简称 NPE)。
Optional类主要用于方法返回类型,以明确表示方法可能返回空值。
Optional 类专用于防止空指针异常(NullPointerException, 简称 NPE)的问题。
Optional 类提供了一种优雅的方式来处理可能为空的值,避免显式进行空值检测,防止空指针异常的产生。通过合理使用 Optional,可以使代码更加简洁、可读性更高。
Optional类的缺点是引入额外的对象的开销。在有些情形不正确使用它,会影响系统性能。
Optional类的特性:
- 非空性:Optional类总是包含一个值或者为空。
- 不可变性:Optional对象一旦创建,就不可更改。
- 链式操作:可以连续调用多个方法,如map、filter等。
二、Optional 类的属性和构造器的定义及创建实例方法:
Optional 类位于 java.util 包中,Optional的属性和构造器部分定义的源代码如下:
public final class Optional<T> {
//两个属性
private static final Optional<?> EMPTY = new Optional<>();//空容器
private final T value; //value 存储容器内唯一元素
//两个构造器
private Optional() { //无参构造方法
this.value = null;
}
private Optional(T value) { //有参构造方法(value不允许为空)
this.value = Objects.requireNonNull(value);
}
}
Optional 类的两个构造器都是私有的,不能直接用构造器创建Optional 类的实例。
创建Optional 类的实例需要使用静态方法,下面是Optional 类创建实例的三个静态方法:
三个静态方法的定义的源码:
//创建值为'null'的Optional 类实例(空容器)的静态方法
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
//创建Optional 类实例(value不允许为空)的静态方法
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
//创建Optional 类实例(value允许为空)的静态方法
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
第三个创建实例的静态方法,实际上是前面二个静态方法的组合应用。
创建实例的示例:
//创建空的 Optional 实例
Optional<String> emptyOpt = Optional.empty();
//创建非空的 Optional 实例
Optional<String> opt = Optional.of("中国");
//创建可以为空的 Optional 实例
Optional<String> nullableOpt = Optional.ofNullable(null);
注意事项:
两个创建Optional实例的静态方法:of() 和 ofNullable() 方法的区别:如果你把 null 值作为参数传递进去,of() 方法会抛出 NullPointerException:
//如此创建Optional<User>,可能是null时,of() 方法会抛出 NullPointerException
Optional<User> optUser = Optional.of(null);
因此,你应该明确对象不为 null 的时候才可使用 of()方法。
如果对象即可能是 null 也可能是非 null,你就应该使用 ofNullable() 方法:
//如此创建Optional<User>,可能是null时,可使用ofNullable()方法
Optional<User> optUser = Optional.ofNullable(null);
三、Optional 类的常用方法:
- Optional 类的四个获取(访问) Optional 对象所包含值的方法(get、orElse、orElseGet和orElseThrow):
T get(): 如果调用对象包含值,返回该值,否则抛异常
T orElse(T other) :如果值存在则将其返回,否则返回指定的other对象。
T orElseGet(Supplier<? extends T> other) :如果有值则将其返回,否则返回由Supplier接口实现提供的对象。
T orElseThrow(Supplier<? extends X> exceptionSupplier) :如果有值则返回该值,否则是空值则抛出由Supplier接口实现提供的异常。
四个获取调用对象所包含值的方法的源码:
//获取容器中唯一元素
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
//获取容器唯一元素,若为空,返回指定值
public T orElse(T other) {
return value != null ? value : other;
}
//获取容器唯一元素,若为空,返回供应者(Supplier)提供的元素
public T orElseGet(Supplier<? extends T> other) {
return value != null ? value : other.get();
}
//如果非空值则返回该值;否则抛出由Supplier接口提供的异常。
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}
说明:
从 Optional 实例中取回实际值对象的方法
方法get() :如果调用对象包含值,返回该值,否则抛异常。要避免异常,你首先要判断Optional容器中是否有值,然后再取;
方法orElse(T other):需要指定空值时返回的类型为T的一个默认值;
方法orElseGet(Supplier<? extends T> other):则需要指定一个供应者,供应一个类型为T的值。
方法orElseThrow(Supplier<? extends X> exceptionSupplier) :则需要指定一个供应者,供应一个异常。
Optional 应用示例一:
Optional<String> opt = Optional.of("江山如画!");
if (opt.isPresent()) {
System.out.println(opt.get());
}
//注意下面二个方法的不同之处
Optional<User> op = Optional.ofNullable(null);
User user1 = op.orElse(new User("张三", 20));
User user = op.orElseGet(() -> new User("张三", 20));
Optional<User> userOpt = Optional.ofNullable(null);
User user = userOpt.orElse(new User("王五",32));
userOpt.orElseThrow(() -> new NullPointerException());
我们修改一下上面的代码,来说明一个性能问题:
//注意下面二个方法的不同之处
Optional<User> op2 = Optional.ofNullable(new User("李四", 20));
User user1 = op2.orElse(new User("张三", 20));
User user = op2.orElseGet(() -> new User("张三", 20));
这个示例中,op2是非空值,最终结果两个 Optional 对象都包含相同的非空值(两个方法也都会返回对应的非空值)。看上去两种方式好像是等价的,但是,orElse() 方法仍然创建了 User 对象。与之相反,orElseGet() 方法不会创建 User 对象。
在执行较密集的调用的情形,例如频繁地调用 Web 服务或数据查询,这个差异会对性能产生重大影响。因此,在实际应用中,还是要权衡利弊选择合适的方法。
- Optional实例值是否为空(值是否存在)的两个判别方法
boolean isPresent() : 判断调用对象的值是否存在。
void ifPresent(Consumer<? super T> consumer) :测试调用对象的值是否存在。如果有值存在,就把该值作为参数传给Consumer接口,并执行Consumer接口的实现代码。
二个判别方法的源码:
public boolean isPresent() {
return value != null;
}
public void ifPresent(Consumer<? super T> consumer) {
if (value != null)
consumer.accept(value);
}
Optional 应用示例二:
//isPresent() 判断是否存在非空的值
Optional<String> opt = Optional.of("江山如画!");
if (opt.isPresent()) {
System.out.println(opt.get());
}
//ifPresent() 检查是否有值存在
opt.ifPresent(System.out::println);
User user = new User(6, "测试用户");
Optional<User> userOpt = Optional.ofNullable(user);
userOpt.ifPresent(u->u.setName("新用户"));
System.out.println(user);
- Optional筛选元素的过滤器方法filter()
如果 Optional 中的值满足测试条件,返回这个 Optional,否则返回一个空的 Optional。
过滤器方法filter()源码:
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent())
return this;
else
return predicate.test(value) ? this : empty();
}
Optional 应用示例三:
Optional<String> longOpt = opt.filter(s -> s.length() > 3);
Optional<User> op = Optional.of(new User("张三", 20));
Optional<User> op2 = op.filter(user -> user.age > 18);
- Optional的映射器方法map()、flatMap()与链式调用
先来学习一下,映射器方法map()和flatMap()的源码:
//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));
}
}
//flatMap()源码
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Objects.requireNonNull(mapper.apply(value));
}
}
提示:
映射器方法map()和flatMap()的方法签名如下所示:
Optional<U> map(Function<? super T, ? extends U> mapper)
Optional<U> flatMap(Function<? super T, Optional<U>> mapper)
这两个映射器方法返回的流(Stream)元素的值都包装在 Optional 中。这就使得对返回值进行链试调用的操作成为可能。
map方法: 对Optional中的值进行转换(若值为空,则map方法什么也不做,直接返回空的Optional对象)。
这个变换基于提供该map的函数,并且这个变换是可选的,如果optional的值为空则不会做任何改变,并且map方法不会改变原始的Optional对象,而返回新的Optional对象,因此可以链式调用进行多个转换操作。
flatMap()方法: 用于扁平化嵌套的Optional结构,以避免引入不必要的嵌套层级。flatmap的映射转换函数返回的必须也是一个Optional对象;flatMap()方法可以用于嵌套的Optional情况,可以将两个互为嵌套关系的Optional对象转换为一个Optional对象。如果原始的Optional对象为空,或转换函数返回的Optional对象为空,那么最终得到的也是空的Optional对象。
两者区别: 若只需要对Optional对象中的值进行转换,而不需要处理嵌套的Optional,那么使用map方法更合适。
如果要对Optional对象进行一些操作返回另外一个Optional对象,flatmap方法更合适。
Optional 应用示例四:
如果 Optional 对象是非空值,则映射后返回一个新的 Optional对象,否则返回空的Optional对象。
//假如names是文件名的数组
Optional<FileInputStream> fis =
names.stream().filter(name -> !isProcessedYet(name))
.findFirst()
.map(name -> new FileInputStream(name));
Optional 应用示例五:处理链式调用
当处理多个可能为空的值时,可以使用链式调用来避免嵌套的 if 判断。
Optional<String> result = Optional.ofNullable(person)
.map(Person::getAddress)
.map(Address::getStreet);
综合实例:
package stream.Optional;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class OptionalExample {
public static void main(String[] args) {
// 创建 Optional 对象
Optional<String> emptyOpt = Optional.empty();
Optional<String> opt = Optional.of("江山如画!");
Optional<String> nullableOpt = Optional.ofNullable(null);
// 检查 Optional 对象
if (opt.isPresent()) {
System.out.println(opt.get());
}
opt.ifPresent(System.out::println);
// 使用 orElse
String value1 = opt.orElse("默认值");
System.out.println(value1);
// 使用 orElseGet
String value2 = opt.orElseGet(() -> "供应一个默认值");
System.out.println(value2);
// 使用 orElseThrow
try {
String value3 = emptyOpt.orElseThrow(() -> new IllegalArgumentException("值缺失"));
} catch (Exception e) {
System.out.println(e.getMessage());
}
// 使用 map
Optional<String> upperOpt = opt.map(String::toUpperCase);
upperOpt.ifPresent(System.out::println);
// 使用 flatMap
Optional<Integer> lengthOpt = opt.flatMap(s -> Optional.of(s.length()));
lengthOpt.ifPresent(System.out::println);
// 使用 filter
Optional<String> longOpt = opt.filter(s -> s.length() > 3);
longOpt.ifPresent(System.out::println);
// 结合 Optional 和流
List<String> names = Arrays.asList("John", "Jane", "Jack", "Doe");
Optional<String> nameOpt = names.stream()
.filter(name -> name.startsWith("J"))
.findFirst();
nameOpt.ifPresent(System.out::println);
// 在实践中使用 Optional
Optional<String> configValue = Optional.ofNullable(getConfig("key"));
configValue.ifPresent(System.out::println);
Optional<User> userOpt = Optional.ofNullable(findUserById(1));
userOpt.ifPresent(System.out::println);
Optional<String> paramOpt = Optional.ofNullable(getRequestParam("param"));
paramOpt.ifPresent(System.out::println);
}
private static String getConfig(String key) {
// 模拟配置项获取
return "value";
}
private static User findUserById(int id) {
// 模拟数据库查询
return new User(id, "John Doe");
}
private static String getRequestParam(String param) {
// 模拟 HTTP 请求参数获取
return "paramValue";
}
}
class User {
private int id;
private String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{ID号=" + id + ", 姓名='" + name + '\'' + '}';
}
}
四、如何使用Optional?
在使用Optional的时候需要考虑一些事情,以决定什么时候如何使用?
Optional不支持 Serializable。因此,在需要序列化进行网络传输和存储的场景中,它不宜用作为类的属性字段。
Optional主要用作方法的返回类型。 在获取到这个类型的实例后,如果它有值,你可以取得这个值,否则可以进行一些替代行为。
Optional类有一个非常有用的场景,就是将其与流(Stream)或其它返回 Optional 的方法结合,以构建流畅的函数式编程的流管道。
我们来看一个示例,使用Stream的函数式编程中使用返回Optional对象的 findFirst() 方法:
List<User> users = new ArrayList<>();
User user = users.stream().findFirst().orElse(new User(15,"默认用户"));
Optional的应用场景:
-
- 方法返回多个结果:当一个方法需要返回多个结果,但其中某些结果可能不存在时。
-
- 处理链式调用:在链式调用中,某些操作可能返回null,使用Optional可以避免空指针异常。
-
- 集合操作:在处理集合时,特别是在需要对集合进行转换或过滤的场合,Optional可以提供更加清晰的逻辑。
-
- 函数式接口:与Function、Predicate等函数式接口结合使用,可以构建更加灵活的数据处理链。
五、借助Optional 类的函数式编程风格的链式调用的应用实例
Optional 是 Java 函数式编程的基础之一,它可帮助在函数式编程在流式的范式中实现。但是 Optional的意义显然不止于此。
当处理多层嵌套的对象时,使用 Optional 可以避免深层次的 null 检查,使代码更加简洁。
我们从一个简单的使用实例开始。在 Java 8 之前,任何访问对象方法或属性的调用都可能导致 空指针异常(NullPointerException):
String isocode = user.getAddress().getCountry().getPostcode().toUpperCase();
上面这行演示代码实现从一个用户(User)中获取邮政编码(postcode)的功能。如果我们需要确保不触发空指针异常,就得在访问每一个值之前对其进行明确的检查:
//普通的面向对象风格的版本
User user = new User(6, "测试用户","315040");
if (user != null) {
Address address = user.getAddress();
if (address != null) {
Country country = address.getCountry();
if (country != null) {
String postcode = country.getPostcode();
if (postcode != null) {
postcode = postcode.toUpperCase();
}
}
}
}
你看到了这个代码是没有Optional时的写法,代码变得冗长,难以阅读,也难以维护。
我们来看看傲使用Optional类如何简明实现这个功能。
从创建和验证实例,到调用其不同的方法,并与其它返回相同类型的方法相结合,下面是见证 Optional奇迹的时刻。
首先,需要重构User类,使其 getter 方法返回 Optional 实例:
public class User {
private Address address;
public User(int id, String name, String postcode) {
this.id = id;
this.name = name;
this.postcode = postcode;
}
public Optional<Address> getAddress() {
return Optional.ofNullable(address);
}
// ...
}
public class Address {
private Country country;
public Optional<Country> getCountry() {
return Optional.ofNullable(country);
}
// ...
}
public class Country {
private Postcode postcode;
public Optional<Postcode> getPostcode() {
return Optional.ofNullable(postcode);
}
// ...
}
上面的User类定义相关的嵌套结构,可以用下面的示意图来表示:
最终,我们把“普通的面向对象风格的版本”转换为“函数式编程风格的版本”,如下:
//函数式编程风格的版本
User user = new User(6, "测试用户","315040");
String result = Optional.ofNullable(user)
.flatMap(User::getAddress)
.flatMap(Address::getCountry)
.map(Country::getPostcode)
.orElse("未知的");
六、Optional错误用法的例子:
- 不适合使用情形之一:直接使用optional与空(null)进行条件判断比较:if (optional != null)
// 错误示例
Optional<String> optional = Optional.ofNullable(null);
if (optional != null) { // 无意义的检查
System.out.println(optional.get());
}
由于Optional本身就是用来避免显式地进行空值(null)检测的,直接显式地用if (optional != null)检查是没有意义的,这个判断语句永远是false。
解决办法:可使用optional.isPresent()或optional.orElse(…)实现此功能。
- 不适合使用情形之二:使用Optional作为方法或构造器的参数。
将Optional对象作为方法或构造器的参数时。这样做会让代码变得复杂,完全没有必要。
User user = new User(56, "测试用户", Optional.empty());
- 不适合使用情形之三:将Optional用于作为集合的参数,集合已经能很好地处理空集合的情形,没必要使用Optional包装集合。
结束语:
Optional是 Java 语言的有益补充它旨在避免空指针异常( NullPointerException),尽管还不能完全消除这些异常。
但Java 8对Optional的精心设计,自然融入了函数式编程,并增强了函数式编程的功能。
参考文献:
- Java高级之告别空指针:Java Optional 详解与实战指南
- Java中的Optional类详细解读
- 理解、学习与使用 Java 中的 Optional