Java新特性:Optional类

2023-10-16 14:31:44 浏览数 (1)

Java新特性:Optional类

Optional 类是 Java 8 才引入的,Optional 是个容器,它可以保存类型 T 的值,或者仅仅保存 null。Optional 提供了很多方法,这样我们就不用显式进行空值检测。Optional 类的引入很好的解决空指针异常。 Java 8 引入 Optional 类,用来解决 NullPointerException。 Optional 代替 if…else 解决空指针问题,使代码更加简洁。

1、Optional类概述
1.1、Optional类介绍

Optional 类是 Java 8 才引入的,Optional 是个容器,它可以保存类型 T 的值,或者仅仅保存 null。Optional 提供了很多方法,这样我们就不用显式进行空值检测。Optional 类的引入很好的解决空指针异常。

Java 8 引入 Optional 类,用来解决 NullPointerException。 Optional 代替 if…else 解决空指针问题,使代码更加简洁。

1.2、Java8之前的空指针异常判断

Java 在使用对象过程中,访问任何方法或属性都可能导致 NullPointerException:

例如我们通过以下方法,获取存在 student 对象中的 Age 值。

代码语言:javascript复制
        public String getIsocode (Student student){
            return student.getAge();
        }

在这样的示例中,如果我们想要避免由 studentstudent.age 为空而导致的空指针问题,我们就需要采用防御式检查减少 NullPointerException(在访问每一个值之前对其进行明确地检查):

代码语言:javascript复制
       public String getIsocode (Student student){
            if (null == student) {
                // doSomething
                return "Unknown";
            }
            if (null = student.getAge()) {
                // doSomething
                return "Unknown";
            }
            return student.getAge();
        }

然而,这种方案并不是很理想,因为为此会多出多个不同的退出点(return),使得代码维护变得艰难,之后每个可能的 null 检查都会新增一个退出点。

为了简化这个过程,我们来看看用 Optional 类是怎么做的。

1.3、Java8之后Optional的使用

当需要判断的量多时,此时的这些判断语句可能会导致代码臃肿冗余,为此 Java8 特意推出了 Optional 类来帮助我们去处理空指针异常。

下面是 Optional 的一些基本用法:

代码语言:javascript复制
@Data
public class Student {
    private Integer age;
}
---
public class Test {
    public static void main(String[] args) {
        // 假设 student 这个对象从数据库中查出的
        Student student = getStudent();
        // 创建一个可接受 null 的 Optiona l类
        Optional<Student> optional = Optional.ofNullable(student);
        // 用法1:获取 student 对象中的某个值,如果不存在的话则取默认值(不具有短路作用)
        Integer a1 = optional.map(Student::getAge).orElse(20));
        // 用法2:获取 student 对象中的某个值,如果不存在的话则取默认值(具有短路作用,因为是懒加载)
        Integer a2 = optional.map(Student::getAge).orElseGet(() -> Integer.MAX_VALUE);
      	// 用法3:判断对象是否存在,不存在则抛出异常
        optional.orElseThrow(() -> new RuntimeException("student不存在!"));
      	// 用法4:判断对象是否存在,存在的话对对象进行操作,例如给对象赋初始值
        optional.ifPresent(o -> o.setAge(18));
      	// 用法5:对象存在时,且年龄满足一定条件容器才会继续保存这对象,否则将会剔除
        optional.filter(o -> o.getAge() > 10);
    }
}

2、Optional类使用
2.1、Optional类常用方法总结

方法

描述

empty

返回一个空的 Optional 实例

filter

如果值存在并且满足提供的谓词,就返回包含该值的 Optional 对象;否则返回一个空的 Optional 对象

flatMap

如果值存在,就对该值执行提供的 mapping 函数调用,返回一个 Optional 类型的值,否则就返回一个空的 Optional 对象

get

如果该值存在,将该值用 Optional 封装返回,否则抛出一个 NoSuchElementException 异常

ifPresent

如果值存在,就执行使用该值的方法调用,否则什么也不做

isPresent

如果值存在就返回 true,否则返回 false

map

如果值存在,就对该值执行提供的mapping 函数调用

of

将指定值用 Optional 封装之后返回,如果该值为 null,则抛出一个 NullPointerException 异常

ofNullable

将指定值用 Optional 封装之后返回,如果该值为 null,则返回一个空的 Optional 对象

orElse

如果有值则将其返回,否则返回一个默认值

orElseGet

如果有值则将其返回,否则返回一个由指定的 Supplier 接口生成的值

orElseThrow

如果有值则将其返回,否则抛出一个由指定的 Supplier 接口生成的异常

2.2、Optional对象创建
2.2.1、Optional.empty()方法

使用 Optional.empty() 方法声明一个空的 Optional:

代码语言:javascript复制
// 通过静态工厂方法 Optional.empty(),创建一个空的 Optional 对象
Optional<Student> optStudent = Optional.empty();
2.2.2、Optional.of(T t)方法

使用 Optional.of(T t) 方法创建一个包含非空值的 Optional 对象 (不推荐):

代码语言:javascript复制
// 静态工厂方法 Optional.of(T t),依据一个非空值创建一个 Optional 对象
Optional<Student> optStudent = Optional.of(student);

如果 student 为 null,这段代码会立即抛出一个 NullPointerException,而不是等到访问 student 的属性值时才返回一个错误。

2.2.3、Optional.ofNullable(T t)方法

使用 Optional.ofNullable(T t) 方法创建一个包含可能为空的值的 Optional 对象 (推荐):

代码语言:javascript复制
// 用静态工厂方法 Optional.ofNullable(T t),你可以创建一个允许 null 值的 Optional 对象
Optional<Student> optStudent = Optional.ofNullable(student);
2.3、Optional对象获取
2.3.1、get()方法

get() 方法,如果变量存在,它直接返回封装的变量值,否则就抛出一个 NoSuchElementException 异常,不推荐使用:

代码语言:javascript复制
optional.map(Student::getAge).get()
2.3.2、orElse(T other)方法

orElse(T other) 方法,它允许你在 Optional 对象不包含值时提供一个默认值:

代码语言:javascript复制
optional.map(Student::getAge).orElse(20));
2.3.3、orElseGet(Supplier<? extends T> other)方法

orElseGet(Supplier<? extends T> other) 方法,它是 orElse 方法的延迟调用版,Supplier 方法只有在 Optional 对象不含值时才执行调用(懒加载):

代码语言:javascript复制
optional.map(Student::getAge).orElseGet(() -> Integer.MAX_VALUE);
2.3.4、orElseThrow(Supplier<? extends X> exceptionSupplier)方法

orElseThrow(Supplier<? extends X> exceptionSupplier) 方法,它和 get 方法非常类似,它们遭遇 Optional 对象为空时都会抛出一个异常,但是使用 orElseThrow 可以定制希望抛出的异常类型:

代码语言:javascript复制
optional.orElseThrow(() -> new RuntimeException("student不存在!"));
2.3.5、ifPresent(Consumer<? super T> consumer)方法

ifPresent(Consumer<? super T> consumer) 方法,它让能在变量值存在时执行一个作为参数传入的方法,否则就不进行任何操作:

代码语言:javascript复制
optional.ifPresent(o -> o.setAge(18));
2.4、Optional对象中值的提取和转换
2.4.1、map()方法

map() 方法,如果值存在,就对该值执行提供的 mapping 函数调用,如果值不存在,则返回一个空的 Optional 对象。

引入 Optional 以前:

代码语言:javascript复制
		String name = null;
    if(insurance != null){
        name = insurance.getName();
    }

引入 Optional 以后:

代码语言:javascript复制
		Optional<String> name = Optional.ofNullable(insurance).map(Insurance::getName);

Optional 的 map 方法和 Java 8 中 Stream 的 map 方法相差无几。

2.4.2、flatMap()方法

flatMap() 方法,对于嵌套式的 Optiona 结构,我们应该使用 flatMap 方法,将两层的 Optional 合并成一个。

我们试着重构以下代码:

代码语言:javascript复制
  public String getCarInsuranceName(Person person) {
      return person.getCar().getInsurance().getName();
  }

由于我们刚刚学习了如何使用 map,我们的第一反应可能是我们可以利用 map 重写之前的代码:

代码语言:javascript复制
  Optional<Person> optPerson = Optional.of(person);
      Optional<String> name =
          optPerson.map(Person::getCar)
                   .map(Car::getInsurance)
                   .map(Insurance::getName);

不幸的是,这段代码无法通过编译。为什么呢? optPerson 是 Optional<Person> 类型的 变量, 调用 map 方法应该没有问题。但 getCar 返回的是一个 Optional<Car> 类型的对象,这意味着 map 操作的结果是一个 Optional<Optional<Car>> 类型的对象。因此,它对 getInsurance 的调用是非法的。

下面应用 map 和 flatMap 对上述示例进行重写:

代码语言:javascript复制
  public String getCarInsuranceName(Optional<Person> person) { 
      return person.flatMap(Person::getCar)
                       .flatMap(Car::getInsurance)
                       .map(Insurance::getName)
                       .orElse("Unknown"); // 如果Optional的结果 值为空设置默认值
  }
2.5、Optional对象其他方法
2.5.1、isPresent()方法

可以使用 isPresent() 方法检查 Optional 对象是否包含非空值,例如:

代码语言:javascript复制
Optional<String> optional = Optional.of("Hello World"); 
if (optional.isPresent()) { 
    System.out.println(optional.get()); 
}
2.5.2、filter()方法

filter() 方法接受一个谓词作为参数。如果 Optional 对象的值存在,并且它符合谓词的条件,filter 方法就返回其值,否则它就返回一个空的 Optional 对象。

比如,你可能需要检查保险公司的名称是否为 “Cambridge-Insurance”。

代码语言:javascript复制
	Insurance insurance = ...;
	if(insurance != null && "CambridgeInsurance".equals(insurance.getName())){
			System.out.println("ok");
  }

使用 Optional 对象的 filter 方法,这段代码可以重构如下:

代码语言:javascript复制
Optional<Insurance> optInsurance = ...;
optInsurance.filter(insurance -> "CambridgeInsurance".equals(insurance.getName()))
            .ifPresent(x -> System.out.println("ok"));
3、Optional注意事项
3.1、Optional的序列化问题

由于 Optiona l类设计时就没特别考虑将其作为类的字段使用,所以它也并未实现 Serializable 接口。由于这个原因,如果你的应用使用了某些要求序列化的库或者框架,在域模型中使用Optional,有可能引发应用程序故障。

然而,我们相信,通过前面的介绍,我们已经看到用 Optional 声明域模型中的某些类型是个不错的主意,尤其是你需要遍历有可能全部或部分为空,或者可能不存在的对象时。如果你一定要实现序列化的域模型,作为替代方案, 我们建议你像下面这个例子那样,提供一个能访问声明为 Optional、变量值可能缺失的接口,代码清单如下:

代码语言:javascript复制
public class Person {
    private Car car;
    public Optional<Car> getCarAsOptional() {
      return Optional.ofNullable(car);
    } 
}
3.2、避免使用基础类型的 Optional 对象

Optional 提供了的一些基础类型 —— OptionalIntOptionalLong 以及 OptionalDouble ,但不推荐大家使用基础类型的 Optional,因为基础类型的 Optional 不支持 map、 flatMap 以及 filter 方法,而这些却是 Optional 类常用的方法。可以使用 Optional<Int>Optional<Long>Optional<Double> 等替代。3. orElse方法的使用

3.3、orElse方法的使用

orElse 中调用的方法一直都会被执行,orElseGet 方法只有在 Optional 对象不含值时才会被调用,所以使用 orElse 方法时需要谨慎, 以免误执行某些不被预期的操作。此种情况下,可使用 orElseGet 方法代替它。

0 人点赞