Optional的概念

java.util.Optional 是java8中引进的一个新的类,它可以对可能缺失的值进行建模,而不是直接将null赋值给变量。
它是用来规范我们开发的API,使其语义更加的明确,使用Optional修饰的对象,表示该对象可能为null。在一定程度上避免了空指针的问题。

Optional的由来

身为java程序员,空指针是最常见的问题了,它是在1965年被一位英国的计算机科学家 Tony Hoare开放的,他设计的初衷是因为采用null的这种引用方式,实现起来非常的容易。但是后来让众多开发人员痛苦不已,这也让他十分的后悔,自称为“价值百万的重大失误”。
每当我们对对象的格式进行检查,判断它的值是否是期望的格式时,却发现我们看到的并不是一个对象,而是一个空指针(java中没有指针,其实就是引用),这时就会抛出NullPointerExceptione的异常。
下面举个列子来验证该问题。

public class Person {
    private Car car;
    
    public Car getCar(){
        return car;
    }

}
class Car{

    private Insurance insurance;

    public Insurance getInsurance() {
        return insurance;
    }
}

class Insurance{

    private String name;
    
    public String getName() {
        return name;
    }
}

以上是要测试的三个类,现在要写一个方法,通过人员来获取保险

 public String getCarInsuranceName(Person person){
        return person.getCar().getInsurance().getName();
    }

上面的代码就会造成空指针的问题,因为有的人没有车,并且有的车没有保险。为了解决空指针的问题,我们常用的方法是防御式编程,可以进行如下的操作:

public String getCarInsuranceName(Person person){
        if (person!=null){
            Car car = person.getCar();
            if (car!=null){
                Insurance insurance = car.getInsurance();
                if (insurance!=null){
                    return insurance.getName();
                }
            }
        }
        return "Unknown";
    }

以上方法每次引用一个变量都会做一次null的检查,这样看似可以避免了空指针,却十分的冗余,因为当不确定一个变量是否为null时,都需要进行if的检查,这不仅增加了代码的可读性,还容易漏掉,并且维护起来也比较困难。
java引入空指针的危害:
1、它是很多问题的错误之源,它是目前开发中最典型的异常。
2、它会使代码膨胀,它会使我们的代码充满了深度嵌套的null检查,代码的可读性下降。
3、它自身是毫无意义的,null自身没有任何的语义,它表示以一种错误的方式对缺失变量的值建模。
4、它破坏了java的哲学,java一直避免引入指针的存在,而唯一的例外就是null指针。
5、它破坏了java的类型,null不属于任何类型,这也意味着它可以被赋值给任意引用类型的变量,我们将无法获取这个null值最初的类型是什么。
而Optional的出现可以让我们最大程度上规避上述问题。

Optional的使用

Optional入门教程

拿最上面的例子来讲,如果我们知道有的人没有车,那就不应该在Personal类内部声明Car的变量,因为Car类型存在,就说明一定会有Car类型的变量,而事实上并不是这样,所以使用Car类型不是一个明智的选择。我们可以使用Optional类对其进行包裹,当存在Car类型变量的时候就返回,当不存在的时候就返回一个空的Optional对象,它就像一个盒子,修饰的对象被放了进去。
除此之外,最重要的是,这就变的非常的明确,用Optional修饰,说明这里是允许变量缺失的。

public class Person {
    //有的人有车,也可能没有车,所以这里用Optional来修饰
    private Optional<Car> car;
    public Optional<Car> getCar(){
        return car;
    }

    public void setCar(Optional<Car> car) {
        this.car = car;
    }
}
class Car{

    //有的车上了保险,也有可能没有上,所以这里使用Optional修饰
    private Optional<Insurance> insurance;

    public Optional<Insurance> getInsurance() {
        return insurance;
    }

    public void setInsurance(Optional<Insurance> insurance) {
        this.insurance = insurance;
    }
}

class Insurance{

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

当我们再次声明方法的时候,可以按如下方式操作:

public class OptionalTest {

    public static void main(String[] args) {
        Person person = new Person();
        Car car = new Car();
        Insurance insurance = new Insurance();
		
		//Optional.of()表示对象不能为null
        insurance.setName("insurance");
        car.setInsurance(Optional.of(insurance));
        person.setCar(Optional.of(car));

        String carInsuranceName = getCarInsuranceName(person);
    }


    public static String getCarInsuranceName(Person person) {
    	//可以通过get方法从Optional中取出值
        return person.getCar().get().getInsurance().get().getName();
    }

}

这样我们就不需要进行null的判断、检查,因为发生异常的时候,会直接在赋值为null的地方进行报错,而不会在调用方法的时候出现空指针。
在这里插入图片描述
需要注意的是:
Optional只是消除了进行null检查的逻辑,并快速定位问题,而不是消除每一个null的引用,相反,它的目标是帮助我们设置更优秀的API,让开发人员看到签名就知道这个变量的含义。
看到这里,有的同学觉得Optional只是形式上声明吗?不能规避空指针吗?答案是可以的,

创建Optional对象

创建Optional的方式有多种
1、声明一个空的Optional

	//使用Optional.empty()方法创建一个空的Car类型的Optional对象。
  Optional<Car> optionalCar = Optional.empty();

2、创建一个非空值的Optional,如果car为null的话,直接抛出空指针异常(参考上面的图片)。

 Car car = new Car();
 Optional<Car> car1 = Optional.of(car);

3、创建一个可以为null的Optional,该方法支持car为null,但是会在用到car的地方抛出异常,但不是空指针异常。

   Car car = new Car();
   Optional<Car> car2 = Optional.ofNullable(car);

在这里插入图片描述

从Optional对象中提取和转换值

虽然我们可以通过get方法从Optional中取出值,但是get方法在遭遇到空的Optional对象时仍然会抛出空指针异常,并没有解决我们的问题。所以我们需要按照约定的方式来使用它。

使用map从Optional对象中提取和转换值

就像stream流一样,这里也是可以通过通过map的方式从Optional中取出元素,map方法返回的也是一个Optional类型的对象,里面的值如果为null的话,可以使用orElse方法赋上自定义的值。

  Insurance insurance = new Insurance();
  insurance.setName("insurance");
  Optional<Insurance> optionalInsurance = Optional.ofNullable(insurance);
  String name = optionalInsurance.map(Insurance::getName).orElse("unKnown");

使用flatMap链接Optional对象

如果想要获取下面最终的值,我们通过map方法来尝试一下

public String getCarInsuranceName(Person person){
        return person.getCar().getInsurance().getName();
    }

当使用map的时候,代码如下:

 public static String getCarInsuranceName(Person person) {
        Optional<Person> optionalPerson = Optional.of(person);
        return optionalPerson.map(Person::getCar)
                .map(Car::getInsurance)
                .map(Insurance::getName)
                .orElse("unKnown");
    }

如上代码是编译不通过的,这里将person进行了Optional来修饰,才可以使用map方法,然后Person里面的Car对象也是用Optional来修饰的,所以使用map方法取出来的是一个用Optional<Optional< Car>>类型的对象,然后再对其使用map方法是行不通的,这里需要使用flatMap方法,就和之前的stream流一样,“将所有内容都放在最外面的容器里”。

   public static String getCarInsuranceName(Person person) {
        Optional<Person> optionalPerson = Optional.of(person);
        return optionalPerson.flatMap(Person::getCar)
                .flatMap(Car::getInsurance)
                .map(Insurance::getName)
                .orElse("unKnown");
    }

为了更清晰的展示map和flatMap的区别,大家可通过下面的demo来理解

        Person person = new Person();
        Optional<Person> optionalPerson = Optional.of(person);
        //map方法
        Optional<Optional<Car>> car1 = optionalPerson.map(Person::getCar);
        //flatMap方法
        Optional<Car> optionalCar1 = optionalPerson.flatMap(Person::getCar);

这里还需要注意的是:
如果对象没有使用Optional来修饰,直接使用get方法就可以拿到对象里面的Optional的值
如果对象使用Opional来修饰的话,就需要使用map方法。
在这里插入图片描述

Optional默认行为

get()

get()方法是最简单也是最不安全的方法,如果变量存在就返回,不存在的话则会抛出NoSuchElementException的异常。所以,get()的使用场景一定是十分确定Optional修饰的值一定是有内容的,否则不建议使用。使用的demo:

  Car car = new Car();
  Insurance insurance = new Insurance();
  car.setInsurance(Optional.of(insurance));
  //使用get来从Optional中取值。
  Insurance insurance1 = car.getInsurance().get();

orElse()

该方法相对于get()的好处在于当Optional对象中不存在则可以返回一个默认的值,使用的demo:

 Car car = new Car();
 Insurance insurance = new Insurance();
 car.setInsurance(Optional.of(insurance));
 //使用orElse来赋予默认的值
 Insurance insurance1 = car.getInsurance().orElse(new Insurance());

orElseGet()

该方法是orElse()方法的延迟调用版,当对象为空的时候才会产生默认值,它的性能相对于orElse()来说更好一些,建议使用该方法,下面是orElseGet()和orElse()的源码对比:

package java.util;
public final class Optional<T> {
    ...
	public T orElse(T other) { 
    	return this.value != null ? this.value : other;
	}
    public T orElseGet(Supplier<? extends T> supplier) {
        return this.value != null ? this.value : supplier.get();
	}
}

orElseThrow()

该方法和get方法很类似,当Optional修饰的对象为空的时候来抛出一个自己指定的异常类型。

ifPresent()

该方法对Optional修饰的对象进行判断,如果存在对象,则在进行某些操作,该方法里面放的是一个Consumer函数式接口,入参为T,void的类型的方法。

    public void ifPresent(Consumer<? super T> consumer) {
        if (value != null)
            consumer.accept(value);
    }

使用demo:

 person.getCar().ifPresent(System.out::println);
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐