【再学一次系列】Optional-消失的空指针

1,033 阅读5分钟

「这是我参与2022首次更文挑战的第6天,活动详情查看:2022首次更文挑战

前言

哈喽大家好,我是卡诺,一名致力于成为全栈的全粘工程师!

作为一名javaer,java.lang.NullPointerException (简称:NPE) 应该是我们最常见的异常之一,为了避免这个问题,通常我们会在可能出现null的地方进行if检测,在某些时候可能造成很深层次的嵌套,导致代码膨胀 (代码数变多) 、可读性变差,维护也变得异常艰难。为了解决这个问题,Java8中引入了Optional类。本章我们将围绕Optional类进行讲解,希望大家通过本章可以对Optional有新的认识!

简介

Optional是一个支持存放泛型类型值的容器对象,其存放的值可以为空,也可以为非空。Optional能通过自身的容器方法判断值是否存在,和获取容器中存放的值。

案例准备

我们准备一个房子(House),房子有门(Door),门包含一个类型(Type),有一个获取类型名称的方法getDoorTypeName

@Data
public class House {

    private Door door;
    
    public String getDoorTypeName() {
        return door.getType().getName();
    }

    @Data
    static class Door {
        private Type type;
    }

    @Data
    static class Type {
        private String name;
    }
}

NPE演示

获取门的类型名称

public void testNPE(){
    System.out.println(new House().getDoorTypeName()); // java.lang.NullPointerException
}

if解决NPE

House中增加getOptimizeDoorTypeName方法,方法内部进行为空判断

public String getOptimizeDoorTypeName() {
    if (door != null) {
        Type type = door.getType();
        if (type != null) {
            return type.getName();
        }
    }
    return "未知类型";
}

上述方法存在一个问题,如果属性嵌套层次比较深,一直用if判断,代码可以说是相当膨胀了,对于这个问题,我们赶快请出本文介绍的主人公Optional吧!

Optional使用指南

Optional是一个final修饰的类,不能被继承。打开源码查看你会发现它的构造函数也是私有的,说明我们也不能在外部使用关键字new进行创建,当然Java的设计者不会自绝后路,Optional提供了更加方便的of和ofNullable来进行构建Optional对象!

Optional容器创建方法

  • 创建方法一览表
方法备注
public static Optional empty()返回一个空容器,内部值为空
public static Optional of(T value)根据value创建一个容器,value不允许为空,如果为空抛出NPE异常
public static Optional ofNullable(T value)根据value创建一个容器,value为空等同于empty()、不为空等同于of()
  • 代码演示
public void testOptional(){
    Optional<House> houseEmpty = Optional.empty();
    Optional<House> houseOfNullable = Optional.ofNullable(null);
    Optional<House> houseOfNotNullable = Optional.ofNullable(new House());
    Optional<House> houseOf = Optional.of(new House());
    Optional<House> houseOfNull = Optional.of(null); // NPE异常
}

Optional容器对象方法

  • 方法一览表
方法备注
public T get()获取Optional的值,值不存在NoSuchElementException异常
public boolean isPresent()判断Optional值是否存在
public void ifPresent(Consumer<? super T> consumer)如果Optional值存在,通过传入的consumer对值进行操作,否则不做任何处理
public T orElse(T other)如果Optional值不为空,则返回该值,否则返回other
public T orElseGet(Supplier<? extends T> other)如果Optional值不为空,则返回该值,否则根据other另外生成一个
public T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X如果Optional值不为空,则返回该值,否则抛出supplier生成的异常
public Optional filter(Predicate<? super T> predicate)值存在,且匹配传入的predicate,则返回包含该值的Optional,否则返回一个空Optional
public Optional map(Function<? super T, ? extends U> mapper)如果Optional设置了value,则调用mapper对值进行处理,并返回的Optional包装的U,否则返回空Optional
public Optional flatMap(Function<? super T, Optional> mapper)与map()方法类似,不过它的mapper结果已经是一个Optional,有值的直接返回Optional,否则返回一个空Optional
  • 代码演示
public void testOptionalMethod(){
    Optional<House> houseOptional = Optional.of(new House());
    House houseHasDoor = new House();
    houseHasDoor.setDoor(new House.Door());
    Optional<House> houseOptionalHasDoor = Optional.of(houseHasDoor);
    Optional<House> houseNullOptional = Optional.empty();
    // get
    System.out.println("get " + houseOptional.get()); // get House(door=null)
    // isPresent
    System.out.println("isPresent " + houseOptional.isPresent()); // isPresent true
    // ifPresent(Consumer<? super T> consumer)
    houseOptional.ifPresent(System.out::println); // House(door=null)
    // orElse
    House houseElse = houseNullOptional.orElse(new House());
    System.out.println("orElse " + houseElse); // orElse House(door=null)
    // orElseGet
    House houseElseGet = houseNullOptional.orElseGet(House::new);
    System.out.println("orElseGet " + houseElseGet); // orElseGet House(door=null)
    // filter
    Optional<House> house = houseOptional.filter(Objects::nonNull);
    Optional<House> houseNull = houseNullOptional.filter(Objects::nonNull);
    System.out.println("filter " + house + " " + houseNull); // filter Optional[House(door=null)] Optional.empty
    // map, flatMap 返回空
    Optional<House.Door> door = houseOptional.map(House::getDoor);
    Optional<House.Door> doorFromFlatMap = houseOptional.flatMap(item -> Optional.of(new House.Door()));
    System.out.println("empty map " + door + " flatMap " + doorFromFlatMap); // empty map Optional.empty flatMap Optional[House.Door(type=null)]
    // map, flatMap 返回有值的容器
    Optional<House.Door> doorOptional = houseOptionalHasDoor.map(House::getDoor);
    Optional<House.Door> doorFromFlatMapOptional = houseOptionalHasDoor.flatMap(item -> Optional.of(new House.Door()));
    System.out.println("has door map " + doorOptional + " flatMap " + doorFromFlatMapOptional); // has door map Optional[House.Door(type=null)] flatMap Optional[House.Door(type=null)]
    // orElseThrow
    houseOptional.orElseThrow(() -> new RuntimeException()); // House(door=null)
    houseNullOptional.orElseThrow(() -> new RuntimeException()); // java.lang.RuntimeException
}

Optional解决NPE

学完Optional,那么回到我们最开始的案例,通过Optional来处理为空问题,House中增加getOptionalDoorTypeName方法

public String getOptionalDoorTypeName() {
    // 将当前door塞入Optional容器
    Optional<Door> door = Optional.ofNullable(this.door);
    // 将类型塞入Optional容器
    Optional<Type> type = Optional.ofNullable(door.orElse(new Door()).getType());
    String name = type.orElse(new Type()).getName();
    return Objects.isNull(name) ?  "未知类型" : name;
}
// 测试
public void testOptionalOptimizeNPE(){
    House house = new House();
    System.out.println(house.getOptionalDoorTypeName()); // 未知类型
    // 设置类型名称
    House.Type type = new House.Type();
    type.setName("玻璃");
    House.Door door = new House.Door();
    door.setType(type);
    house.setDoor(door);
    System.out.println(house.getOptionalDoorTypeName()); // 玻璃
}

更优雅的写法

上述解决方案基于House增加一个getOptionalDoorTypeName方法,方法内部的实现看起来依然是很臃肿,因此更建议在类的属性本身进行处理,代码如下:

Data
public class OptionalHouse {

    private Optional<Door> door = Optional.empty();

    public String getDoorTypeName() {
        String name = door.orElse(new Door()).getType().orElse(new Type()).getName();
        return Objects.isNull(name) ?  "未知类型" : name;
    }

    @Data
    public static class Door {
        private Optional<Type> type = Optional.empty();
    }

    @Data
    public static class Type {
        private String name;
    }
}

源码

总结

  • 本章主要介绍Optional,Optional是一个支持存放泛型类型值的容器对象,值可以为空,也可以为非空;
  • Optional通过静态方法创建Optional对象,部分对象方法如filter、map等用法类似Stream;
  • 使用Optional可以有效的减少if的嵌套判断,增加代码的可读性。

关联文章

👉【再学一次系列】

最后

  • 感谢铁子们耐心看到最后,如果大家感觉本文有所帮助,麻烦给个赞👍关注➕
  • 由于本人技术有限,文章和代码可能存在错误,希望大家评论指出,万分感激🙏;
  • 同时也欢迎大家V我一起讨论学习前端、Java知识,一起卷一起进步。