> 技术文档 > 《Java Optional详解:JDK8避免空指针的最佳实践》

《Java Optional详解:JDK8避免空指针的最佳实践》


前言

● 该文档旨在介绍Java中Optional类的各种API的使用说明,主要涉及到Optional对象的创建、流式转换、条件执行、取值方法等;
● 该文档基于JDK 1.8

概述

Optional在JDK 1.8中首次引入,目的是为了解决空指针异常,提升代码的可读性和可维护性。在实际业务中,Optional常用于确保链式调用中的安全访问。
为了实现以上功能,Optional中提供了以下四种类型的方法:创建方法、流式转换方法、条件判断方法以及取值方法。另外该类中包含一个value字段,代表对象实例所持有的内容。

创建方法(创建Optional对象)

创建方法是指这三种方法:empty()、of()、ofNullable()。empty()的返回值为一个value字段为空的Optional对象;而of()和ofNullable()则会根据传入的参数返回一个响应的Optional对象。
of()和ofNullable()的主要区别在于传入参数value能否为空,虽然Optional的设计目的就是为了解决空指针异常,但是如果在of()中传入的参数为null,仍然会直接抛出空指针异常,所以在不确定value是否会为空的情况下,应该使用ofNullable()来创建一个Optional对象。

Object obj = null;//会产生空指针异常Optional<Object> opt1 = Optional.of(obj);//返回一个字段value为null的Optional对象Optional<Object> opt2 = Optional.ofNullable(obj);

实际上,ofNullable()只是对传入参数做了一个判断并调用了empty()方法或of()方法。

public static <T> Optional<T> ofNullable(T value) { return value == null ? empty() : of(value);}

流式转换方法(对Optional中的value进行处理)

流式转换方法包括:filter()、flatMap()map()。这些方法会根据传入的方法对当前的value进行过滤或者转换,通常建议使用Lambda表达式或者方法应用来表示传入的方法。需要注意的是对Optional.empty()使用的所有转换方法都会跳过,这也是为什么使用Optional可以避免空指针异常。

Optional.of(userList) //过滤年龄小于18的用户 .filter(user -> user.getAge > 18) //获取用户所在的城市 .map(user -> user.getCity) //获取城市名 .map(City::getName) //如果过程中存在空对象则返回空字符串 .orElse(\"\"); 

flatMap()相比于map()主要是用于避免产生Optional的嵌套,例如下面这个场景就更加适合使用flatMap()。

class User{ private String name; public Optional<String> getName(){ //使用Optional封装用户名避免后续操作出现空指针异常 return Optional.ofNullable(this.name); }}User user = new User();int length = Optional.of(user)  //如果直接使用map,则返回值为Optional<Optional>  //后面继续调用.map(String::length)时则会出现异常  .flatMap(User::getName)  .map(String::length)  .orElse(0);

条件判断方法(判断Optional对象value是否为空)

条件判断方法包括:isPresent()、ifPresent()。因其返回值不为Optional,所以不能继续进行链式操作。如果还要继续处理可以使用map方法。isPresent()返回值为boolean类型,ifPresent()没有返回值。后者可以等价于:

Optional<User> opt = Optional.of(new User(\"zhangsan\",18));//省略一系列操作...//判断Optional对象value是否为空if(opt.isPresent()){ //取出其中的value并执行某些操作 doSomething(opt.get())}//以上操作等价于opt.ifPresent(user -> doSomething(user));//或者opt.map(user -> { doSomething(user); return user;})....;//可以继续其他操作

取值方法(获取Optional对象value中的内容)

取值方法包括get()、orElse()、orElseGet()、orElseThrow()。其中get()表示直接取出Optional对象中的value,但是如果value为空则会抛出空指针异常,所以不推荐直接使用该方法。
orElseThrow()在value为空时会抛出异常,通过自定义异常可以更快速地定位问题产生位置。

public void method(UserParam params){ Optional.of(params) .filter(param -> param.getAge>18) .orElseThrow(()->BusinessException.causeBy(AccountError.CHECK_FAILED));}

orElse()与orElseGet()会先对当前的value进行判断,如果为空则会返回其传入的默认值,不为空则返回value;两者的差别在于两者传入的参数的加载时间,orElse()一定会执行其传入的参数,orElseGet()只有在value确实为空时才会执行。在下面的情况下,显然orElseGet()会有更好的性能。

private String getResult(){ //模拟执行一系列操作 Thread.sleep(1000); ... return \"\"}Optional opt = Optional.of(new User());//getResult()会被执行String result1 = opt.orElse(getResult());//getResult()不会被执行String result2 = opt.orElseGet(getResult());

需要注意的是,只有在调用取值方法后,Optional才会将value放回,否则拿到的始终是一个Optional对象,例如下面的场景中如果不调用取值方法就会产生类型转换异常。

public class DyOpusInfo { /** * 正文图片链接 */ @JSONPath(value = {\"images\"}, castValue = ImageCast.class) private List<String> images; //省略其它字段与注册方法 ... public static class ImageCast implements CastValue { @Override public Object cast(Object value) { return Optional.of(value)  .map(data -> (JSONArray) data)  .map(array -> array.toList(JSONObject.class))  //在调用该方法后链式调用就结束了,  //所以这里的返回值实际上是Optional<List>  //但是因为方法的返回值是Object,所以可以通过类型检查  //不过在后续的转换中就会产生异常  .map(array -> array.stream() .map(jsonObject -> jsonObject.getJSONArray(\"url_list\")) .filter(Objects::nonNull) .map(jsonArray -> jsonArray.getString(0)) .collect(Collectors.toList())); } }}

Optional类核心功能总结

Optional 为我们提供了一种更加优雅的空值处理方式。结合不同场景,推荐的使用习惯如下:

  1. 对象创建:优先使用 ofNullable() 来代替 of(),避免在不确定值是否为空时抛出 ullPointerException。

  2. 链式操作:当可能返回 Optional 类型时,使用 flatMap() 替代 map(),以避免出现 Optional 的嵌套。

  3. 条件处理:优先考虑 ifPresent() 或 map() 来完成后续逻辑,代码更简洁。

  4. 取值方式:更推荐 orElse() / orElseGet() 来提供默认值,且在需要提升性能时优先使用 orElseGet()。

  5. 异常定位:在需要明确提示错误的场景,优先使用 orElseThrow(),配合自定义异常,便于快速定位问题。

  6. 开发习惯:不要将 Optional 用作类的字段,而应仅在方法返回值中使用它,作为一种结果包装与防御性编程手段。

通过以上原则,可以在保持代码简洁的同时,有效减少空指针异常的发生,并提升整体的可维护性和可读性。