
在上一篇介绍Stream流式数据处理的文章中提到了Optional类,这是Java 8新增的一个类,用以解决程序中常见的NullPointerException异常问题。本篇文章将详细介绍Optional类,以及如何用它消除代码中的null检查。
避免使用null检查
作为Java开发人员,几乎所有人都遇到过NullPointerException异常,大多数人遇到NullPointerException异常时都会在异常出现的地方加上if代码块来判断值不为空,比如下面的代码:
1 | public void bindUserToRole(User user) { |
这是比较普遍的做法,为了避免出现NullPointerException异常,手动对可能为null值进行了处理,只不过代码可读性就没那么好,业务逻辑都被淹没在if逻辑判断中,重构一下看起来会好一点:
1 | public void bindUserToRole(User user) { |
上面的代码避免了深层的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对象:
1 | Optional<String> emptyOpt = Optional.empty(); |
empty()方法创建的对象没有值,如果对emptyOpt变量调用isPresent()方法会返回false,调用get()方法抛出NullPointerException异常。
of()方法使用一个非空的值创建Optional对象:
1 | String str = "Hello World"; |
ofNullable()方法接收一个可以为null的值:
1 | Optional<String> nullableOpt = Optional.ofNullable(str); |
如果str的值为null,得到的nullableOpt是一个没有值的Optional对象。
提取Optional对象中的值
如果我们要获取User对象中的roleId属性值,常见的方式是直接获取:
1 | String roleId = null; |
使用Optional中提供的map()方法可以以更简单的方式实现:
1 | Optional<User> userOpt = Optional.ofNullable(user); |
map()方法与Stream API中的map()一样,类似于映射操作,将原始类型映射为一个新的类型。
使用orElse()方法获取值
Optional类还包含其他方法用于获取值,这些方法分别为:
orElse():如果有值就返回,否则返回一个给定的值作为默认值;orElseGet():与orElse()方法作用类似,区别在于生成默认值的方式不同。该方法接受一个Supplier<? extends T>函数式接口参数,用于生成默认值;orElseThrow():与前面介绍的get()方法类似,当值为null时调用这两个方法都会抛出NullPointerException异常,区别在于该方法可以指定抛出的异常类型。
下面来看看这三个方法的具体用法:
1 | String str = "Hello World"; |
此外,Optional类还提供了一个ifPresent()方法,该方法接收一个Consumer<? super T>函数式接口,可以对值进行一些操作:
1 | Optional<String> strOpt = Optional.of("Hello World"); |
使用filter()方法过滤
filter()方法可用于判断Optional对象是否满足给定条件,一般用于条件过滤:
1 | Optional<String> optional = Optional.of("lw900925@163.com"); |
在上面的代码中,如果filter()方法中的Lambda表达式成立,filter()方法会返回当前Optional对象值,否则,返回一个值为空的Optional对象。
如何正确使用Optional
我发现好像很多人(别说还真的很多)没有掌握Optional的正确使用方法,比如我一个同事喜欢写这样的Optional代码:
1 | Optional<User> userOpt = Optional.ofNullable(user); |
说实话,这样的写法跟传统的if语句判断空值没有任何区别,没有起到Optional的正真作用(中枪的同学举手):
1 | if (user != null) { |
所以,当我们从之前版本切换到Java 8的时候,不应该还按照以前的思维方式处理null值,Java 8提倡函数式编程,新增的许多API都可以用函数式编程表示,Optional类也是其中之一。这里有几条关于Optional使用的建议:
- 尽量避免在程序中直接调用
Optional对象的get()和isPresent()方法; - 避免使用
Optional类型声明实体类的属性;
第一条建议中直接调用get()方法是很危险的做法,如果Optional的值为空,那么毫无疑问会抛出NullPointerException异常,而为了调用get()方法而使用isPresent()方法作为空值检查,这种做法与传统的用if语句块做空值检查没有任何区别。
第二条建议避免使用Optional作为实体类的属性,它在设计的时候就没有考虑过用来作为类的属性,可以查看Optional的源代码,你会发现它没有实现java.io.Serializable接口,也就是说如果你用到一些orm框架的二级缓存,使用Optional作为实体类的属性没法被序列化。
下面我们通过一些例子讲解Optional的正确用法:
正确创建Optional对象
上面提到创建Optional对象有三个方法,empty()方法比较简单,没什么特别要说明的。主要是of()和ofNullable()方法。当你很确定一个对象不可能为null的时候,应该使用of()方法,否则,尽可能使用ofNullable()方法,比如:
1 | public static void method(Role role) { |
orElse()方法的使用
1 | return str != null ? str : "Hello World" |
上面的代码表示判断字符串str是否为空,不为空就返回,否则,返回一个常量。使用Optional类可以表示为:
1 | return strOpt.orElse("Hello World") |
简化if-else
1 | User user = ... |
上面的代码可以简化成:
1 | User user = ... |
再比如判断用户名不能重复的逻辑,根据用户名从数据库中查询一个用户,如果不为null就抛出异常告诉前端用户已存在:
1 | User existUser = userDAO.findById(user.getUsername()); |
可以简写为:
1 | User user = userDAO.findById(user.getId()); |
总结一下,新的Optional类让我们可以以函数式编程的方式处理null值而不用嵌套很多if-else逻辑。下一篇文章将介绍Java 8中新添加的日期API。