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对象。

相关推荐
陌上少年,且听这风吟3 分钟前
【已解决】SpringBoot3项目整合Druid依赖:Druid监控页面404报错
java·spring boot·spring
code.song5 分钟前
电影评论|基于springBoot的电影评论网站设计与实现(附项目源码+论文+数据库)
数据库·spring boot·后端
洗发水很好用11 分钟前
新版IDEA提示@Autowired不建议字段注入
java·ide·intellij-idea
model200512 分钟前
sahi目标检测java实现
java·算法·目标检测
LG.YDX35 分钟前
java:练习
java
给自己做减法1 小时前
排序算法快速记忆
java·算法·排序算法
Dovir多多1 小时前
渗透测试入门学习——php与mysql数据库连接、使用session完成简单的用户注册、登录
前端·数据库·后端·mysql·安全·html·php
Flying_Fish_roe1 小时前
Spring Boot-WebSocket相关问题
spring boot·后端·websocket
计算机学姐1 小时前
基于微信小程序的食堂点餐预约管理系统
java·vue.js·spring boot·mysql·微信小程序·小程序·mybatis
骆晨学长1 小时前
基于springboot学生健康管理系统的设计与实现
java·开发语言·spring boot·后端·spring