NullPointException应该算是每一个码农都很熟悉的家伙了吧?谁的代码不曾抛过几个空指针异常呢…

比如:你写了段如下的代码:


public void getCompanyFromEmployee() {Employee employee = getEmployee();Company company = employee.getTeam().getDepartment().getCompany();System.out.println(company);
}private Employee getEmployee() {Employee employee = new Employee();employee.setEmployeeName("JiaGouWuDao");employee.setTeam(new Team("DevTeam4"));return employee;
}

运行程序,你可能就等不到你需要的结果,而是要喜提NullPointException了…

作为JAVA开发中最典型的异常类型,甚至可能是很多程序员入行之后收到的第一份异常大礼包类型。而NullPointException也似乎成为了一种魔咒,迫使程序员在敲出的每一行代码的时候都需要去思考下是否需要去做一下判空操作,久而久之,代码中便充斥着大量的null检查逻辑。

于是呢,上面的代码会变成下面这样:


public void getCompanyFromEmployee() {Employee employee = getEmployee();if (employee == null) {// do something here...return;}Team team = employee.getTeam();if (team == null) {// do something here...return;}Department department = team.getDepartment();if (department == null) {// do something here...return;}Company company = department.getCompany();System.out.println(company);
}

是不是大家的项目中都有见过这种写法的?每行代码中都流露着对NullPointException的恐惧有木有?是不是像极了一颗被深深伤害过的心在小心翼翼的保护着自己?

null的困扰

通过上面代码示例,我们可以发现使用null可能会带来的一系列困扰:

  • 空指针异常,导致代码运行时变得不可靠,稍不留神可能就崩了
  • 使代码膨胀,导致代码中充斥大量的null检查与保护,使代码可读性降低

此外,null还有一个明显的弊端:

  • 含义不明确,比如一个方法返回了null,调用方不清楚到底是因为逻辑有问题导致为null,还是说null其实也是一种可以接受的正常返回值类型?

所以说,一个比较好的编码习惯,是尽量避免在程序中使用null,可以按照具体的场景分开区别对待:

  • 确定是因为代码或者逻辑层面处理错误导致的无值,通过throw异常的方式,强制调用方感知并进行处理对待
  • 如果null代表业务上的一种正常可选值,可以考虑返回Optional来替代。

当然咯,有时候即使我们自己的代码不返回null,也难免会遇到调用别人的接口返回null的情况,这种时候我们真的就只能不停的去判空来保护自己吗?有没有更优雅的应对策略来避免自己掉坑呢?下面呢,我们一起探讨下null的一些优雅应对策略。

Optional应对null处理

Optional一定比return null安全吗

前面我们提到了说使用Optional来替代null,减少调用端的判空操作压力,防止调用端出现空指针异常。

那么,使用返回Optional对象就一定会比return null更靠谱吗?

答案是:也不一定,关键要看怎么用!

比如:下面的代码,getContent()方法返回了个Optional对象,然后testCallOptional()方法作为调用方,获取到返回值后的操作方式:


public void testCallOptional() {Optional<Content> optional = getContent();System.out.println("-------下面代码会报异常--------");try {// 【错误用法】直接从Optional对象中get()实际参数,这种效果与返回null对象然后直接调用是一样的效果Content content = optional.get();System.out.println(content);} catch (Exception e) {e.printStackTrace();}System.out.println("-------上面代码会报异常--------");
}private Optional<Content> getContent() {return Optional.ofNullable(null);
}

上述代码运行之后会发现报错了:

-------下面代码会报异常--------
java.util.NoSuchElementException: No value presentat java.util.Optional.get(Optional.java:135)at com.veezean.skills.optional.OptionalService.testCallOptional(OptionalService.java:47)at com.veezean.skills.optional.OptionalService.main(OptionalService.java:58)
-------上面代码会报异常--------

既然直接调用Optional.get()报错,那就是调用前加个判断就好咯?


public void testCallOptional2() {Optional<Content> optional = getContent();// 使用前先判断下元素是否存在if (optional.isPresent()) {Content content = optional.get();System.out.println(content);}
}

执行一下,果然不报错了。但是,这样真的就是解决方法吗?这样跟直接返回null然后使用前判空(下面的写法)其实也没啥区别,也并不会让调用方使用起来更加的优雅与靠谱:


public void testNullReturn2() {Content content = getContent2();if (content != null) {System.out.println(content.getValue());}
}

那怎么样才是正确的使用方式呢,下面一起来看下。

全面认识下Optional

创建Optional对象

Optional<T>对象,可以用来表示一个T类型对象的封装,或者也可以表示不是任何对象。Optional类提供了几个静态方法供对象的构建:

方法名 功能含义描述
empty() 构造一个无任何实际对象值的空Optional对象(可以理解为业务层面的null
of(T t) 根据给定的对象,构造一个此对象的封装Optional对象,注意入参t不能为null,否则会空指针
ofNullable(T t) 根据传入的入参t的值构造Optional封装对象,如果传入的t为null,则等同于调用empty()方法,如果t不为null,则等同于调用of(T t)方法

在项目中,我们可以选择使用上面的方法,实现Optional对象的封装:


public void testCreateOptional() {// 使用Optional.of构造出具体对象的封装Optional对象System.out.println(Optional.of(new Content("111","JiaGouWuDao")));// 使用Optional.empty构造一个不代表任何对象的空Optional值System.out.println(Optional.empty());System.out.println(Optional.ofNullable(null));System.out.println(Optional.ofNullable(new Content("222","JiaGouWuDao22")));
}

输出结果:


Optional[Content{id='111', value='JiaGouWuDao'}]
Optional.empty
Optional.empty
Optional[Content{id='222', value='JiaGouWuDao22'}]

这里需要注意下of方法如果传入null会抛空指针异常,所以比较建议大家使用ofNullable方法,可以省去调用前的额外判空操作,也可以避免无意中触发空指针问题:

Optional常用方法理解

在具体讨论应该如何正确使用Optional的方法前,先来了解下Optional提供的一些方法:

方法名 含义说明
isPresent 如果Optional实际有具体对象值,则返回true,否则返回false。
ifPresent 这是一个函数式编程风格的API接口,入参是一个函数,即如果Optional对象有实际对象值,则会执行传入的入参函数逻辑,如果不存在实际对象值,则不会执行传入的入参函数逻辑。
get 返回Optional封装的实际对象T数据,注意,如果实际对象数据不存在,会抛异常而非返回null
orElse get方法类似,都是获取Optional实际的对象值,区别在于orElse必须传入一个默认值,当Optional没有实际值的时候返回默认值而非抛异常
orElseGet 可以理解为orElse方法的升级版,区别在于orElse仅允许传入一个固定的默认值,而orElseGet的入参是一个函数方法,当Optional无实际值时,会执行给定的入参函数,返回动态值
orElseThrow orElse类似,区别在于如果没有获取到,会抛出一个指定的异常
filter 判定当前Optional的实际对象是否符合入参函数的过滤规则,如果符合则返回当前Optional对象,如果不符合则返回空Optional
map 接收一个入参函数,允许将Optional中的实际对象值处理转换为另一实际对象值(这个入参函数的返回值为T),并生成返回此新类型的Optional对象,如果生成的新对象为null,则返回一个空Optional对象
flatMap map类似,区别点在于入参函数的返回值类型有区别(此处入参函数的返回值为Optional<T>

看到这里的mapflatMap方法,不知道大家会不会联想到Stream流对象操作的时候也有这两个方法的身影呢(不了解的同学可以戳这个链接抓紧补补课:吃透JAVA的Stream流操作)?的确,它们的作用也是类似的,都是用来将一个对象处理转换为另一个对象类型的:

对于Optional而言,mapflatMap最终的实现效果其实都是一样的,仅仅只是入参的要求不一样,也即两种不同写法,两者区别点可以通过下图来理解:

实际使用的时候,可以根据需要选择使用map或者flatMap


public void testMapAndFlatMap() {Optional<User> userOptional = getUser();Optional<Employee> employeeOptional = userOptional.map(user -> {Employee employee = new Employee();employee.setEmployeeName(user.getUserName());// map与flatMap的区别点:此处return的是具体对象类型return employee;});System.out.println(employeeOptional);Optional<Employee> employeeOptional2 = userOptional.flatMap(user -> {Employee employee = new Employee();employee.setEmployeeName(user.getUserName());// map与flatMap的区别点:此处return的是具体对象的Optional封装类型return Optional.of(employee);});System.out.println(employeeOptional2);
}

从输出结果可以看出,两种不同的写法,实现是相同的效果:


Optional[Employee(employeeName=JiaGouWuDao)]
Optional[Employee(employeeName=JiaGouWuDao)]

Optional使用场景

减少繁琐的判空操作

再回到本篇文章最开始的那段代码例子,如果我们代码里面不去逐个做判空保护的话,我们可以如何来实现呢?看下面的实现思路:


public void getCompanyFromEmployeeTest() {Employee employeeDetail = getEmployee();String companyName = Optional.ofNullable(employeeDetail).map(employee -> employee.getTeam()).map(team -> team.getDepartment()).map(department -> department.getCompany()).map(company -> company.getCompanyName()).orElse("No Company");System.out.println(companyName);
}

先通过map的方式一层一层的去进行类型转换,最后使用orElse去获取Optional中最终处理后的值,并给定了数据缺失场景的默认值。是不是看着比一堆if判空操作要舒服多了?

是时候优雅的和NullPointException说再见了相关推荐

  1. 在德国如何优雅地和同事说再见

    在德国这种什么都讲究流程的国度,辞职时可不是随随便便拍屁股走人那么简单.职场中如何优雅地和同事说再见,是一个你必须重视的问题. 德国职场中,从公司离职时,举办同事送别Party(Abschiedspa ...

  2. Java基础学习总结(142)——以正确的姿势使用Java 8 Optional

    分享一个大神的人工智能教程.零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到人工智能的队伍中来!点击浏览教程 Java 8 增加 Optional. 来优雅的解决 NullPointExcept ...

  3. 补贴与付费:如何完成从「补贴」到「付费」的过渡

    ---- / BEGIN / ---- 前段时间的美团和滴滴大战,又让人再次见识了疯狂补贴的力量. 近些年的互联网产品,几乎宠坏了用户,烧钱是常态,红包.优惠券.免费送礼等补贴往往是营销人员最喜欢的方 ...

  4. 有关《三体》的一篇拓展短小说

    <为此 ,我们可以冒险> 我叫张素榕,红岸基地的工作者,一个一无所知的科研人员. 和我共同工作的叶文洁,是一个沉寂而淡漠的人.她秀气的脸庞上似乎有一种若即若离的恍惚.我曾努力去接触她,在多 ...

  5. java8——Optinal类

    Optional介绍 我们知道 Java 8 增加了一些很有用的 API, 其中一个就是 Optional. 如果对它不稍假探索, 只是轻描淡写的认为它可以优雅的解决 NullPointExcepti ...

  6. 我们要在离职时,优雅地说再见!

    ‍‍ 作者 | DougArcuri 译者 | 弯月   责编 | 孙胜 出品 | CSDN(ID:CSDNnews) 作为软件工程经理,我也会遇到人情世故的难题.最近一位团队成员提出了离职申请,是因 ...

  7. 再见!该死的NullPointException

    文章来源:[公众号:架构悟道] 目录 背景 null 的困扰 Optional 应对 null 使用抛异常替代 return null JDK 与开源框架的实践 总结 背景 NullPointExce ...

  8. 再见Spring Security!推荐一款功能强大的权限认证框架,用起来够优雅!

    ‍ ‍在我们做SpringBoot项目的时候,认证授权是必不可少的功能!我们经常会选择Shiro.Spring Security这类权限认证框架来实现,但这些框架使用起来有点繁琐,而且功能也不够强大. ...

  9. Python程序员Debug利器,和Print说再见 | 技术头条

    整理 | Rachel 责编 | Jane 出品 | Python大本营(id:pythonnews) [导语]程序员每日都在和 debug 相伴.新手程序员需要学习的 debug 手段复杂多样,设置 ...

最新文章

  1. path与classpath区别 path是Windows查找.exe文件的路径;classpath是jvm查找.class文件的路径
  2. 白话Elasticsearch71-ES生产集群部署之各个节点以daemon模式运行以及优雅关闭
  3. MySQL Performance-Schema(三) 实践篇
  4. SAP ABAP关键字在Chrome浏览器里高亮显示的实现原理 - How is ABAP keyword highlight implemented in Chrome
  5. Android控件系列之XML静态资源
  6. yolov5-detect.py解析与重写
  7. 六款练手的javaweb项目源码!
  8. 模式识别与机器学习(国科大2021-2022秋季学期课程)-基础概念及算法
  9. Sublime快捷键大全
  10. vue-property-decorator
  11. BlackBerry上网初体验
  12. Hopkins Statistic判断irir数据集聚类性能
  13. dubbo mysql_sofa或dubbo
  14. html展开插件,分享10款功能强大的HTML5/CSS3应用插件
  15. 新一代云上基础技术和架构分论坛
  16. swd只能下载一次第二次出现错误
  17. 【NOI2011】bzoj2434 阿狸的打字机
  18. 山东什么企业适合做两化融合
  19. TurboMosaic for Mac(蒙太奇马赛克图片制作软件)
  20. python 点到直线的距离

热门文章

  1. 魔性,用Python实现火爆全网的「蚂蚁呀嘿」视频特效!
  2. app软件开发、uniapp、uni-admin使用,独立开发app,相关技术一体化(很完整,小编实践过的一这套uniapp相关技术)
  3. C语言 题目 1724: 后缀子串排序
  4. 在Maya、ZBrush和UE中制作龙香炉
  5. #3498. 无限剑制(ubw)
  6. 3ds max基础材质初学者必读(22)——使用光线跟踪材质
  7. PPT神技能,如何使用PPT制作炫酷视频?我想你还是不会吧!
  8. 头条号如何提升互动度
  9. MySQL8.0 连接失败部分原因
  10. IntelliJ IDEA插件系列:四大炫酷神器你值得拥有