一、前言
这里引用书中描述来介绍Optional类:
Optional是为核心类库设计的一个数据类型,用来替换
null
值。人们对原有的null
值有很多抱怨,甚至连发明这一概念的Tony Hoare也是如此,他曾说这是自己的一个"价值连城的错误"。作为一名有影响力的科学家就是这样:虽然连一毛钱也见不到,却也可以犯一个"价值连城的错误"。-- 摘自 Java 8函数编程
人们常常使用null
值表示值不存在,Optional对象能更好地表达这个概念。使用null
代表值不存在的最大问题在于NullPointerException
。一旦引用一个存储null
值的变量,程序会立即崩溃。使用Optional对象有两个目的:首先,Optional对象鼓励程序员适时检查变量是否为空,以避免代码缺陷;其次,它将一个类的API中可能为空的值文档化,这比阅读实现代码要简单很多。-- 摘自 Java 8函数编程
基于上述问题,Google公司著名的Guava项目引入了Optional类,Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到Google Guava的启发,Optional类已经成为Java 8类库的一部分。
java.util.Optional
是Java 8引入的一个容器类,目的是解决NullPointerException
的问题,它可以保存<T>
的值,代表这个值存在,或者仅仅保存null
,表示这个值不存在。原来用null
表示一个值不存在,现在Optional可以更好的表达这个概念。
本质上,Optional是一个包装类,其中包含对其它对象的引用。这种情况下,对象只是指向内存位置的指针,并且也可以指向任何内容。
缺点:会造成代码过于冗长,最主要是会引入额外的对象开销,所以适当使用即可。
二、Optional特性
java
public final class Optional<T> {
//Null指针的封装
private static final java.util.Optional<?> EMPTY = new java.util.Optional<>();
//内部包含的值对象
private final T value;
private Optional() ;
//返回EMPTY对象
public static<T> java.util.Optional<T> empty() ;
//构造函数,但是value为null,会报NPE
private Optional(T value);
//静态工厂方法,但是value为null,会报NPE
public static <T> java.util.Optional<T> of(T value);
//静态工厂方法,value可以为null
public static <T> java.util.Optional<T> ofNullable(T value) ;
//获取value,但是value为null,会报NoSuchElementException
public T get() ;
//返回value是否为null
public boolean isPresent();
//如果value不为null,则执行consumer式的函数,为null不做事
public void ifPresent(Consumer<? super T> consumer) ;
//过滤,如果value不为null,则根据条件过滤,为null不做事
public java.util.Optional<T> filter(Predicate<? super T> predicate) ;
//转换,在其外面封装Optional,如果value不为null,则map转换,为null不做事
public<U> java.util.Optional<U> map(Function<? super T, ? extends U> mapper);
//转换,如果value不为null,则map转换,为null不做事
public<U> java.util.Optional<U> flatMap(Function<? super T, java.util.Optional<U>> mapper) ;
//value为null时,默认提供other值
public T orElse(T other);
//value为null时,默认提供other值
public T orElseGet(Supplier<? extends T> other);
//value为null时,默认提供other值
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) ;
}
Optional类提供了大约10种方法,我们可以使用它们来创建和使用Optional类,下面将介绍如何使用它们。
三、Optional用法
3.1 empty
创建一个值为空的Optional对象。
java
Optional<String> empty = Optional.empty();
如果对象为空,请避免使用==
与Optional.empty()
返回的实例比较,因为不能保证它是一个单例,应该使用isPresent方法。
3.2 of
创建一个特定的非空值Optional对象。
java
String name = "java";
Optional<String> opt = Optional.of(name);
静态方法需要一个非null
参数;否则将引发NullPointerException
异常。如果我们不知道参数是否为null
,可以使用ofNullable方法。
3.3、ofNullable
创建一个(兼容null
值)Optional对象。
java
Optional<String> optional = Optional.ofNullable(null);
如果我们传入一个空引用,它不会抛出NullPointerException
异常,而是返回一个空的Optional对象。
3.4 isPresent
如果Optional对象所包含的值为null
,则返回false,反之返回true。通常在对对象执行任何其他操作之前,先在Optional上调用此方法。
java
Optional<String> optional = Optional.of("value");
if (optional1.isPresent()){
//Do something, normally a get
}
3.5 isEmpty
如果Optional对象所包含的值为null
,则返回true,反之返回false(这与isPresent相反,并且仅在Java 11及更高版本中可用)。
java
Optional<String> optional = Optional.of("value");
if (optional1.isEmpty()){
//Do something
}
3.6 ifPresent
如果Optional对象存在值,则使用该值调用指定的使用者;否则什么都不做。
java
Optional<String> optional = Optional.of("value");
optional1.ifPresent(s -> System.out.println(s.length()));
补充:ifPresent和isPresent方法有相似之处,但它们的用途略有不同:
- ifPresent:如果Optional对象中存在非空值,则执行给定的消费者操作(Consumer)。这个方法不返回任何值,主要用于执行某个操作而不关心操作的结果。
- isPresent:判断Optional对象中是否存在非空值,并返回一个布尔值。这个方法通常用于在存在非空值时执行一段代码,在不存在非空值时执行另一段代码。
3.7 get
用于获取Optional中的值,如果此Optional中存在值则返回该值,否则抛出NoSuchElementException
(这就可以使用orElse方法来紧急救援)。
java
Optional<String> optional1 = Optional.of("value");
if (optional1.isPresent()){
String value = optional1.get();
}
3.8 orElse和orElseGet
orElse和orElseGet是两个常用的方法,用于当Optional中的值为空时,为Optional提供默认值。
示例:假设下面getDefaultValue方法返回值为default value,那么当Optional对象值为null时,通过orElse和orElseGet方法补充默认值后,最后都会输出default value。
java
Optional<String> optional = Optional.ofNullable(null);
// 使用 orElse 方法
String result1 = optional.orElse(getDefaultValue());
System.out.println(result1); // 输出:default value
// 使用 orElseGet 方法
String result2 = optional.orElseGet(this::getDefaultValue);
System.out.println(result2); // 输出:default value
1、相同点:
在上面的代码中,orElse和orElseGet方法的行为是相同的。它们都只有在Optional对象中没有值时才会调用getDefaultValue方法来获取默认值。
2、不同点:
参数不同:orElse方法接受一个对象作为参数,orElseGet方法接受一个Supplier接口类型的对象作为参数。
性能不同:当你使用orElse方法时,作为参数的默认值对象会在调用该方法时立即被创建或计算。这意味着无论Optional对象中是否有值,默认值对象都会被创建或计算。
相反,使用orElseGet方法时,getDefaultValue方法只有在Optional对象中没有值时才会被调用。在下面例子中,因为Optional对象中已经有了一个非空的值,getDefaultValue方法并不会被调用。
假设上述getDefaultValue方法它执行了一个非常耗时的操作来生成一个默认值,而Optional中已经存在非空的值,使用orElse方法时getDefaultValue方法仍然会被调用,尽管它的结果并不会被使用。这种情况下,使用orElse方法可能会浪费资源。
总的来说,orElseGet方法在需要懒惰地计算默认值或者提高代码可读性时更为适用。
3.9 orElseThrow
除了上述基本用法之外,Optional类还提供了一种强大的orElseThrow方法。这个方法接受一个Supplier接口类型的对象作为参数,该对象提供了一个异常实例,当Optional对象值为空时,该方法会抛出制定的异常(可以为自定义异常)。
java
Optional<String> optional = Optional.ofNullable(null);
try {
String result = optional.orElseThrow(() -> new Exception("No value present"));
System.out.println(result);
} catch (Exception e) {
System.out.println(e.getMessage()); // 输出:No value present
}
在上面的代码中,orElseThrow方法被用来在Optional对象中没有值时抛出一个Exception。如果Optional对象中有值,orElseThrow方法会返回该值;否则,它会抛出由Supplier提供的异常。
orElseThrow方法的签名如下:
java
public T orElseThrow(Supplier<? extends X> exceptionSupplier)
其中,T
是Optional对象中可能存在的值的类型,X
是要抛出的异常的类型。
需要注意的是,orElseThrow方法并不是在所有情况下都适用。它应该只在遇到无法恢复的错误时使用,例如在程序逻辑上不允许出现空值的情况下。如果你只是想提供一个默认值,应该使用orElse或orElseGet方法。
此外,orElseThrow方法在Java 10中被简化了,可以直接传入一个异常类,而不需要使用Supplier:
java
Optional<String> optional = Optional.ofNullable(null);
try {
String result = optional.orElseThrow(Exception.class);
System.out.println(result);
} catch (Exception e) {
System.out.println(e.getMessage()); // 输出:No value present
}
这个简化版的orElseThrow方法会在Optional对象中没有值时抛出一个NoSuchElementException异常,异常消息是"No value present"。
总的来说,orElseThrow方法可以帮助你在处理可能为空的值时更加明确地表达你的意图,并在必要时抛出一个异常来处理错误情况。
四、最后
Optional 对象不仅可以用于新的Java 8 API,也可用于具体领域类中,和普通的类别无二致。当试图避免空值相关的缺陷,如未捕获的异常时,可以考虑一下是否可使用Optional对象。