如何优雅的消除代码里的NullPointerException!
NPE(NullPointerException)是我们代码工作中最常遇到的一个异常,非常的难受,如何优雅的处理它呢。
本篇文章将详细介绍Optional类,以及如何用它消除代码中的null检查。Optional是为了防止NullPointerException,使代码更优雅。
避免使用null
检查
作为Java开发人员,几乎所有人都遇到过NullPointerException
异常,大多数人遇到NullPointerException
异常时都会在异常出现的地方加上if
代码块来判断值不为空,比如下面的代码:
public void bindUserToRole(User user) {if (user != null) {String roleId = user.getRoleId();if (roleId != null) {Role role = roleDao.findOne(roleId);if (role != null) {role.setUserId(user.getUserId());roleDao.save(role);}}}
}
这是比较普遍的做法,为了避免出现NullPointerException
异常,手动对可能为null
值进行了处理,不过代码看起来非常糟糕,业务逻辑被淹没在if
逻辑判断中,也许下面的代码看起来可读性稍好一些:
public String bindUserToRole(User user) {if (user == null) {return;}String roleId = user.getRoleId();if (roleId == null) {return;}Role = roleDao.findOne(roleId);if (role != null) {role.setUserId(user.getUserId());roleDao.save(role);}
}
上面的代码避免了深层的if
语句嵌套,但本质上是一样的,方法内有三个不同的返回点,出错后调试也不容易,因为你不知道是那个值导致了NullPointerException
异常。
基于上面的原因,Java 8中引入了一个新的类Optional
,用以避免使用null
值引发的种种问题。
Optional
类
java.util.Optional<T>
类是一个封装了Optional
值的容器对象,Optional
值可以为null
,如果值存在,调用isPresent()
方法返回true
,调用get()
方法可以获取值。
创建Optional
对象
Optional
类提供类三个方法用于实例化一个Optional
对象,它们分别为empty()
、of()
、ofNullable()
,这三个方法都是静态方法,可以直接调用。
empty()
方法用于创建一个没有值的Optional
对象:
Optional<String> emptyOpt = Optional.empty();
empty()
方法创建的对象没有值,如果对emptyOpt
变量调用isPresent()
方法会返回false
,调用get()
方法抛出NullPointerException
异常。
of()
方法使用一个非空的值创建Optional
对象:
String str = "Hello World";
Optional<String> notNullOpt = Optional.of(str);
ofNullable()
方法接收一个可以为null
的值:
Optional<String> nullableOpt = Optional.ofNullable(str);
如果str
的值为null
,得到的nullableOpt
是一个没有值的Optional
对象。
提取Optional
对象中的值
如果我们要获取User
对象中的roleId
属性值,常见的方式是直接获取:
String roleId = null;
if (user != null) {roleId = user.getRoleId();
}
使用Optional
中提供的map()
方法可以以更简单的方式实现:
Optional<User> userOpt = Optional.ofNullable(user);
Optional<String> roleIdOpt = userOpt.map(User::getRoleId);
使用orElse()
方法获取值
Optional
类还包含其他方法用于获取值,这些方法分别为:
orElse()
:如果有值就返回,否则返回一个给定的值作为默认值;orElseGet()
:与orElse()
方法作用类似,区别在于生成默认值的方式不同。该方法接受一个Supplier<? extends T>
函数式接口参数,用于生成默认值;orElseThrow()
:与前面介绍的get()
方法类似,当值为null
时调用这两个方法都会抛出NullPointerException
异常,区别在于该方法可以指定抛出的异常类型。
下面来看看这三个方法的具体用法:
String str = "Hello World";
Optional<String> strOpt = Optional.of(str);
String orElseResult = strOpt.orElse("Hello Shanghai");
String orElseGet = strOpt.orElseGet(() -> "Hello Shanghai");
String orElseThrow = strOpt.orElseThrow(() -> new IllegalArgumentException("Argument 'str' cannot be null or blank."));
此外,Optional
类还提供了一个ifPresent()
方法,该方法接收一个Consumer<? super T>
函数式接口,一般用于将信息打印到控制台:
Optional<String> strOpt = Optional.of("Hello World");
strOpt.ifPresent(System.out::println);
使用filter()
方法过滤
filter()
方法可用于判断Optional
对象是否满足给定条件,一般用于条件过滤:
Optional<String> optional = Optional.of("lw900925@163.com");
optional = optional.filter(str -> str.contains("164"));
在上面的代码中,如果filter()
方法中的Lambda表达式成立,filter()
方法会返回当前Optional
对象值,否则,返回一个值为空的Optional
对象。
如何正确使用Optional
通过上面的例子可以看出,Optional
类可以优雅的避免NullPointerException
带来的各种问题,不过,你是否真正掌握了Optional
的用法?假设你试图使用Optional
来避免可能出现的NullPointerException
异常,编写了如下代码:
Optional<User> userOpt = Optional.ofNullable(user);
if (userOpt.isPresent()) {User user = userOpt.get();// do something...
} else {// do something...
}
坦白说,上面的代码与我们之前的使用if
语句判断空值没有任何区别,没有起到Optional
的正真作用:
if (user != null) {// do something...
} else {// do something...
}
当我们从之前版本切换到Java 8的时候,不应该还按照之前的思维方式处理null
值,Java 8提倡函数式编程,新增的许多API都可以用函数式编程表示,Optional
类也是其中之一。这里有几条关于Optional
使用的建议:
- 尽量避免在程序中直接调用
Optional
对象的get()
和isPresent()
方法; - 避免使用
Optional
类型声明实体类的属性;
第一条建议中直接调用get()
方法是很危险的做法,如果Optional
的值为空,那么毫无疑问会抛出NullPointerException
异常,而为了调用get()
方法而使用isPresent()
方法作为空值检查,这种做法与传统的用if
语句块做空值检查没有任何区别。
第二条建议避免使用Optional
作为实体类的属性,它在设计的时候就没有考虑过用来作为类的属性,如果你查看Optional
的源代码,你会发现它没有实现java.io.Serializable
接口,这在某些情况下是很重要的(比如你的项目中使用了某些序列化框架),使用了Optional
作为实体类的属性,意味着他们不能被序列化。
下面我们通过一些例子讲解Optional
的正确用法:
正确创建Optional
对象
上面提到创建Optional
对象有三个方法,empty()
方法比较简单,没什么特别要说明的。主要是of()
和ofNullable()
方法。当你很确定一个对象不可能为null
的时候,应该使用of()
方法,否则,尽可能使用ofNullable()
方法,比如:
public static void method(Role role) {// 当Optional的值通过常量获得或者通过关键字new初始化,可以直接使用of()方法Optional<String> strOpt = Optional.of("Hello World");Optional<User> userOpt = Optional.of(new User());// 方法参数中role值不确定是否为null,使用ofNullable()方法创建Optional<Role> roleOpt = Optional.ofNullable(role);
}
orElse()
方法的使用
return str != null ? str : "Hello World"
上面的代码表示判断字符串str
是否为空,不为空就返回,否则,返回一个常量。使用Optional
类可以表示为:
return strOpt.orElse("Hello World")
简化if-else
User user = ...
if (user != null) {String userName = user.getUserName();if (userName != null) {return userName.toUpperCase();} else {return null;}
} else {return null;
}
上面的代码可以简化成:
User user = ...
Optional<User> userOpt = Optional.ofNullable(user);return userOpt.map(User::getUserName).map(String::toUpperCase).orElse(null);
总结一下,新的Optional
类让我们可以以函数式编程的方式处理null
值,抛弃了Java 8之前需要嵌套大量if-else
代码块,使代码可读性有了很大的提高。
坑
使用OPtional的orElse()问题
项目中有这样一段代码:
return Optional.ofNullable(service.A()).orElse(service.B())
功能显而易见,service.A()如果返回值是null,则返回service.B(),否则直接返回service.A()。
实际使用中发现:
如果service.A()返回非null,最终结果是service.A(),然而service.B()这个方法也被执行了。这样肯定就不对了,如果service.B()中还有插入数据库或者RPC这种操作,问题就大了。刚开始还以为是什么执行顺序问题,后来在Stack Overflow上看到老外讨论orElse()和orElseGet()的区别,其中一点区别就是orElse(T)无论前面Optional容器是null还是non-null,都会执行orElse里的方法,orElseGet(Supplier)并不会,如果service无异常抛出的情况下,Optional使用orElse或者orElseGet的返回结果都是一样的
stack overflow上有人还给出这样一个例子
static String B() {System.out.println("B()...");return "B";
}public static void main(final String... args) {System.out.println("-----");System.out.println(Optional.of("A").orElse(B()));System.out.println("-----");System.out.println(Optional.of("A").orElseGet(() -> B()));
}// 输出结果
-----
B()...
A
-----
A
看了上面代码,我就把我的代码改成如下即可:
return Optional.ofNullable(service.A()).orElseGet(() -> service.B())
结论:Optional的orElse(T)若方法不是纯计算型的,有与数据库交互或者远程调用的,都应该使用orElseGet()
引自:
使用Optional的orElse()问题 - liubingyu12345的博客
Java - Difference between `Optional.orElse()` and `Optional.orElseGet()` - Stack Overflow
如何优雅的消除代码里的NullPointerException!相关推荐
- 一统江湖的大前端(5)editorconfig + eslint——你的代码里藏着你的优雅
[摘要]<一统江湖的大前端>系列是自己的前端学习笔记,旨在介绍javascript在非网页开发领域的应用案例和发现各类好玩的js库,不定期更新. 如果你对前端的理解还是写写页面绑绑事件,那 ...
- 写作技巧:如何在word里优雅的插入代码
在现实生活中,我们有时需要在word里插入代码,如果直接复制,缩进哪些会出问题.今天如何讲解下如何优雅的插入代码 第一步:打开网站 http://www.planetb.ca/syntax-highl ...
- 如何正确的使用Java8中的Optional类来消除代码中的null检查
点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 作者:一书生VOID lw900925.github.io/jav ...
- 使用Optional类来消除代码中的null检查
Python实战社群 Java实战社群 长按识别下方二维码,按需求添加 扫码关注添加客服 进Python社群▲ 扫码关注添加客服 进Java社群▲ 作者丨一书生VOID lw900925.github ...
- 使用Java8中的Optional类来消除代码中的null检查
作者:一书生VOID lw900925.github.io/java/java8-optional.html Optional类是Java 8新增的一个类,用以解决程序中常见的NullPointe ...
- [译]编写优雅的JavaScript代码 - 最佳实践
[原文]: devinduct.com/blogpost/22- 有没有似曾相识 如果你对于代码,除了关注是否能准确的执行业务逻辑,还关心代码本身是怎么写的,是否易读,那么你应该会关注如何写出干净优雅 ...
- 优化:更优雅的异步代码?
异步问题 回调地狱 异步编程中最常见的一种问题便是回调地狱. 单次ajax请求有多个回调响应 $.ajax({type: 'get',url: '/path/to/calldata',success: ...
- 使用Spring特性优雅书写业务代码
作者:阿里巴巴淘系技术 链接:https://www.zhihu.com/question/60761181/answer/1737592739 来源:知乎 著作权归作者所有.商业转载请联系作者获得授 ...
- 消除代码中的坏味道,编写高质量代码
消除代码中的坏味道,编写高质量代码 Intro 想要写出较好的代码,保证代码的高质量需要时刻警惕代码中的坏味道,今天分享一下,我觉得平时写的代码中可能会出现的坏味道代码的一些示例 常见的坏味道代码 B ...
最新文章
- proftpd登陆速度慢的解决[转]
- mysql 开发 生产_在没有表锁定的情况下在巨大的MySQL生产表...
- websocket的用途/场景
- opencv方框内图像保存_opencv::将两幅图像合并后,在同一个窗口显示;并将合并的图像流保存成视频文件...
- 五种Linux桌面操作过程录制的文件
- FineReport10.0功能说明
- ACDsee_14中文许可证秘钥
- [转帖]妇联4剧透 看不起电影看剧透.
- date类型被fastjson变为long类型一串数字
- 超详细IPONE越狱教程详解
- 静态后台管理系统的搭建
- vs 2019生成类试图
- 详解minigui图片加载及显示
- 拼多多API分享:抓取拼多多商品详情页数据
- git回退到上一个版本:
- 1024共码未来(一览中华风华,API First)
- 一种求周期二元线性序列的极小多项式的方法
- python爬虫笔记四:大众点评店铺信息(字体反爬-静态映射)
- WINCE读取系统数据或系统配置信息
- ClassNotFoundException:org.exolab.castor.xml.XMLException
热门文章
- 多多情报通:拼多多两天没发货会怎样处理?延迟交货怎么办?
- Python 超简单格式化代码,仅需一行命令
- 介绍一款Excel数据收集软件,简单好用
- Xson:Java对象序列化和反序列化工具
- java实现环形数组
- 《 ROP EDGE : TOWARDS DEEP GRAPH CONVOLU - TIONAL NETWORKS ON NODE CLASSIFICATION》
- 数据库 嵌套查询_联接操作与嵌套查询 数据库管理系统
- 非匀质化资金池——为什么资产 NFT 化是 DeFi 的必经之路
- ESP32 直接使用WIFI进行UDP通信, 将光敏电阻传感器数值, 显示在OLED上 - 米思齐/Arduino
- Zookeeper简单介绍