《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 为我们提供了一种更加优雅的空值处理方式。结合不同场景,推荐的使用习惯如下:
-
对象创建:优先使用
ofNullable()
来代替 of(),避免在不确定值是否为空时抛出 ullPointerException。 -
链式操作:当可能返回 Optional 类型时,使用
flatMap()
替代 map(),以避免出现 Optional 的嵌套。 -
条件处理:优先考虑
ifPresent()
或 map() 来完成后续逻辑,代码更简洁。 -
取值方式:更推荐
orElse()
/orElseGet()
来提供默认值,且在需要提升性能时优先使用 orElseGet()。 -
异常定位:在需要明确提示错误的场景,优先使用
orElseThrow()
,配合自定义异常,便于快速定位问题。 -
开发习惯:不要将 Optional 用作类的字段,而应仅在方法返回值中使用它,作为一种结果包装与防御性编程手段。
通过以上原则,可以在保持代码简洁的同时,有效减少空指针异常的发生,并提升整体的可维护性和可读性。