Java 8 Optional用法【总结记录】

一、前言

这里引用书中描述来介绍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方法有相似之处,但它们的用途略有不同:

  1. ifPresent:如果Optional对象中存在非空值,则执行给定的消费者操作(Consumer)。这个方法不返回任何值,主要用于执行某个操作而不关心操作的结果。
  2. 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对象。

相关推荐
檀越剑指大厂30 分钟前
【Java基础】使用Apache POI和Spring Boot实现Excel文件上传和解析功能
java·spring boot·apache
苹果酱056734 分钟前
Golang的网络流量分配策略
java·spring boot·毕业设计·layui·课程设计
小青柑-35 分钟前
Go语言中的接收器(Receiver)详解
开发语言·后端·golang
孑么1 小时前
GDPU Android移动应用 重点习题集
android·xml·java·okhttp·kotlin·android studio·webview
未命名冀2 小时前
微服务面试相关
java·微服务·面试
Heavydrink2 小时前
ajax与json
java·ajax·json
阿智智2 小时前
纯手工(不基于maven的pom.xml、Web容器)连接MySQL数据库的详细过程(Java Web学习笔记)
java·mysql数据库·纯手工连接
fangxiang20082 小时前
spring boot 集成 knife4j
java·spring boot
C++小厨神3 小时前
Bash语言的计算机基础
开发语言·后端·golang
BinaryBardC3 小时前
Bash语言的软件工程
开发语言·后端·golang