使用Optional摆脱NPE的折磨

architectural-architectural-design-architecture

在目前的工做中,我对Java中的Stream和Lambda表达式都使用得不少,以前也写了两篇文章来总结对应的知识。java

不过对于Optional这个特性,一直没有很好地使用起来,因此最近又开始阅读《Java 8实战》这本书,本文是针对其中第10章的一个学习总结。面试

背景

在Java中,若是你尝试对null作函数调用,就会引起NullPointerException(NPE),NPE是Java程序开发中的最典型的异常,对于Java开发者来讲,不管你是初出茅庐的新人和还工做多年的老司机,NPE常常让他们翻车。为了不NPE,他们会加不少if判断语句,使得代码的可读性变得不好。数据库

从软件设计的角度来看,null自己是没有意义的语义,这是一种对缺失变量值的错误的建模。编程

从Java类型系统的角度看,null能够被赋值给任何类型的变量,而且不断被传递,知道最后谁也不知道它是从哪里引入的。后端

Optional的引入

Java设计者从Haskell和Scala中获取灵感,在Java 8中引入了一个新的类java.util.Optional<T>。若是一个接口返回Optional ,能够表示一我的可能有车也可能没有车,这个比简单的返回Car要更明确,阅读代码的人不须要提早准备业务知识。 设计模式

Optional的目的就在于此:经过类型系统让你的领域模型中隐藏的知识显式地体如今你的代码中。api

Optional的使用

方法 描述
empty 返回一个空的Optional实例
filter 若是值存在而且知足提供的过滤条件,则返回包含该值的Optional对象;不然就返回一个空的Optional对象
map 若是值存在,就对该值执行提供的mapping函数调用
flatMap 若是值存在,就对该值执行提供的mapping函数调用,返回一个Optional类型的值,不然就返回一个空的Optional对象
ifPresent 若是值存在,就执行使用该值的方法调用,不然什么也不作
of 将指定值用Optional封装以后返回,若是该值为null,则抛出一个NPE
ofNullable 将指定值用Optional封装以后返回,若是该值为null,则返回一个空的Optional对象
orElse 若是有值则返回,不然返回一个默认值
orElseGet 若是有值则返回,不然返回一个由指定的Supplier接口生成的值(若是默认值的生成代价比较高的话,则适合使用orElseGet方法)
orElseThrow 若是有值则返回,不然返回一个由指定的Supplier接口抛出的异常
get 若是值存在,则返回该值,不然抛出一个NoSuchElementException异常
isPresent 若是值存在则返回true,不然返回false

上面这张表里列举了Optional的基础API,我这里列举了一些使用的tips:安全

  • 你能够用ofNullable将一个可能为null的对象封装为Optional对象,而后获取值的时候使用orElse方法提供默认值;可使用empty方法建立一个空的Optional对象;of方法通常不用,不过若是你知道某个值不可能为null,则能够用Optional封装该值,这样它一旦为null就会抛出异常。
//empty方法的使用
Optional<Car> optCar = Optional.empty();

//of方法的使用
Optional<Car> optCar = Optional.of(car);

//ofNullable方法的使用
Optional<Car> optCar = Optional.ofNullable(car);
  • 你可使用map方法从Optional 对象中它封装的值中的某个字段的值;
Optional<Insurance> optInsurance = Optional.ofNullable(insurance);
Optional<String> name = optInsurance.map(Insurance::getName);
  • 若是须要连续、层层递进的从某个对象链的末端获取字段的值,则不能所有使用map方法,须要先使用flatMap,最后再使用map方法;
//转换以前
public String getCarInsuranceName(Person person) {
  return person.getCar().getInsurance().getName();
}

//转换后
public String getCarInsuranceName(Optional<Person> person) {
  return person.flatMap(Person::getCar)
               .flatMap(Car::Insurance)
               .map(Insurance::getName)
               .orElse("Unknown");
}
  • Optional中的map、flatMap和filter方法,在概念是与Stream中对应的方法都很相似,区别就在于Optional中的元素至多有一个,算是Stream的一种特殊状况——一种特殊的集合。
  • 不要使用ifPresent和get方法,它们本质上和不适用Optional对象以前的模式相同,都是臃肿的if-then-else判断语句;
  • 因为Optional没法序列化,因此在领域模型中,没法将某个字段定义为Optional 的,缘由是:Optional的设计初衷仅仅是要支持能返回Optional对象的语法,若是咱们但愿在域模型中引入Optional,则能够用下面这种替代的方法:
public class Person {
  private Car car;
  public Optional<Car> getCarAsOptional() {
    return Optional.ofNullable(car);
  }
}
  • 不要使用基础类型的Optional对象,缘由是:基础类型的Optional对象不支持map、flatMap和filter方法,而这些方法是Optional中很是强大的方法。

实战案例

案例1:使用工具类方法改良可能抛出异常的API

Java方法处理异常结果的方式有两种:返回null(或错误码);抛出异常,例如:Integer.parseInt(String)这个方法——若是没法解析到对应的整型,该方法就抛出一个NumberFormationException,这种状况下咱们通常会使用try/catch语句处理异常状况。app

通常咱们建议将try/catch块单独提取到一个方法中,在这里使用Optional设计这个方法,代码以下。在开发中,能够尝试构建一个OptionalUtility工具类,将这些复杂的try/catch逻辑封装起来。函数式编程

public static Optional<Integer> stringToInt(String a) {
  try{
    return Optional.of(Integer.parseInt(s));
  } catch  (NumberFormationException e) {
    return Optional.empty();
  }
}

案例2:综合案例

如今有个方法,是尝试从一个属性映射中获取某个关键词对应的值,例子代码以下:

public static int readDuration(Properties properties, String name) {
        String value = properties.getProperty(name);
        if (value != null) {
            try {
                int i = Integer.parseInt(value);
                if (i > 0) {
                    return i;
                }
            } catch (NumberFormatException e) {

            }
        }
        return 0;
    }

使用Optional的写法后,代码以下所示:

public static int readDurationWithOptional(Properties properties, String name) {
        return Optional.ofNullable(properties.getProperty(name))
            .flatMap(OptionalUtility::stringToInt)
            .filter(integer -> integer > 0)
            .orElse(0);
    }

若是须要访问的属性值不存在,Properites.getProperty(String)方法的返回值就是一个null,使用noNullable工厂方法就能够将该值转换为Optional对象;接下来,可使用flatMap将一个Optional 转换为Optional 对象;最后使用filter过滤掉负数,而后就可使用orElse获取属性值,若是拿不到则返回默认值0。

总结

使用Optional的思路和Stream相同,都是链式思路,跟数据库查询似的,表达力很强,并且省去了哪些复杂的try/catch和if-then-else方法。在后面的开发中,可使用Optional设计API,这样能够设计出更安全的接口和方法。


本号专一于后端技术、JVM问题排查和优化、Java面试题、我的成长和自我管理等主题,为读者提供一线开发者的工做和成长经验,期待你能在这里有所收获。javaadu

相关文章
相关标签/搜索