资讯专栏INFORMATION COLUMN

使用Optional摆脱NPE的折磨

BDEEFE / 756人阅读

摘要:在目前的工作中,我对中的和表达式都使用得很多,之前也写了两篇文章来总结对应的知识。为了避免,他们会加很多判断语句,使得代码的可读性变得很差。在后面的开发中,可以使用设计,这样可以设计出更安全的接口和方法。

在目前的工作中,我对Java中的Stream和Lambda表达式都使用得很多,之前也写了两篇文章来总结对应的知识。

024:Java流实现Shell:cat 1.log | grep a | sort | uniq -c | sort -rn

函数式编程让你忘记设计模式

不过对于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。如果一个接口返回Optional,可以表示一个人可能有车也可能没有车,这个比简单的返回Car要更明确,阅读代码的人不需要提前准备业务知识。

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

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 optCar = Optional.empty();

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

//ofNullable方法的使用
Optional optCar = Optional.ofNullable(car);

你可以使用map方法从Optional对象中它封装的值中的某个字段的值;

Optional optInsurance = Optional.ofNullable(insurance);
Optional name = optInsurance.map(Insurance::getName); 

如果需要连续、层层递进的从某个对象链的末端获取字段的值,则不能全部使用map方法,需要先使用flatMap,最后再使用map方法;

//转换之前
public String getCarInsuranceName(Person person) {
  return person.getCar().getInsurance().getName();
}

//转换后
public String getCarInsuranceName(Optional 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 getCarAsOptional() {
    return Optional.ofNullable(car);
  }
}

不要使用基础类型的Optional对象,原因是:基础类型的Optional对象不支持map、flatMap和filter方法,而这些方法是Optional中非常强大的方法。

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

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

一般我们建议将try/catch块多带带提取到一个方法中,在这里使用Optional设计这个方法,代码如下。在开发中,可以尝试构建一个OptionalUtility工具类,将这些复杂的try/catch逻辑封装起来。

public static Optional 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面试题、个人成长和自我管理等主题,为读者提供一线开发者的工作和成长经验,期待你能在这里有所收获。

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/75392.html

相关文章

  • 通过JDK 8避免NPE,可选

    摘要:从开始,您可以通过返回来避免。例如,此方法返回一个空的,而不是返回可选获取指定的卡片内容的代码从JDK 8开始,您可以通过返回Optional来避免NullPointerException。 例如,此方法返回一个空的Optional:,而不是返回null public可选fetchShoppingCart(long id){      ShoppingCart cart = //获取...

    lavor 评论0 收藏0
  • Java8实用技能

    大概一年多之前,我对java8的理解还仅限一些只言片语的文章之上,后来出于对函数式编程的兴趣,买了本参考书看了一遍,然后放在了书架上,后来,当我接手大客户应用的开发工作之后,java8的一些工具,对我的效率有了不小的提升,因此想记录一下java8的一些常用场景,我希望这会成为一个小字典,能让我免于频繁翻书,但是总能找到自己想找的知识。 用于举例的model: @Data public class ...

    microcosm1994 评论0 收藏0
  • 给女朋友讲解什么是Optional【JDK 8特性】

    摘要:接口例子如果容器的对象存在,则对其执行调用函数得到返回值。上面一句代码对应着最开始的老写法方法直接看源码方法与方法类似,区别在于函数的返回值不同。 前言 只有光头才能变强 前两天带女朋友去图书馆了,随手就给她来了一本《与孩子一起学编程》的书,于是今天就给女朋友讲解一下什么是Optional类。 至于她能不能看懂,那肯定是看不懂的。(学到变量/for循环的女人怎么能看懂呢) 不知道大家还...

    caspar 评论0 收藏0
  • 【修炼内功】[Java8] 使用Optional正确姿势及序列化问题

    摘要:本文已收录修炼内功跃迁之路的为解决空的问题带来了很多新思路,查看源码,实现非常简单,逻辑也并不复杂。 本文已收录【修炼内功】跃迁之路 showImg(https://segmentfault.com/img/bVbrCvp?w=852&h=480); Java8的Optional为解决空的问题带来了很多新思路,查看Optional源码,实现非常简单,逻辑也并不复杂。Stuart Ma...

    Ajian 评论0 收藏0
  • Java8新特性总览

    摘要:新特性总览标签本文主要介绍的新特性,包括表达式方法引用流默认方法组合式异步编程新的时间,等等各个方面。还有对应的和类型的函数连接字符串广义的归约汇总起始值,映射方法,二元结合二元结合。使用并行流时要注意避免共享可变状态。 Java8新特性总览 标签: java [TOC] 本文主要介绍 Java 8 的新特性,包括 Lambda 表达式、方法引用、流(Stream API)、默认方...

    mayaohua 评论0 收藏0

发表评论

0条评论

BDEEFE

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<