跟着 Guava 学 Java 之 Optional

使用和避免 null

Google 底层代码库,95%的集合类不接受 null 值作为元素。 相比默默地接受 null,使用快速失败操作拒绝 null 值对开发者更有帮助。

很多 Guava 工具类对 Null 值都采用快速失败操作,此外,Guava 还提供了很多工具类,让你更方便地用特定值替换 Null 值

例子

我们知道 JDK8 以后 也参考 Guava 加入了 Optional的 API,使用上跟 Guava 的区别不大,例子中我们使用 JDK 的 API 来演示。

直接上个实际工作中的案例即:“对象的嵌套判空”

比如我有个对象,对象的某个属性也是对象,然后就这样一直嵌套下去,比如:

@Data
public class Test1{

    private String info="info1";
    private Test2 test2;
}

@Data
public class Test2 {

    private String info;
    private Test3 test3;
}

@Data
public class Test3 {

    private String info;
    private Test4 test4;
}

@Data
public class Test4 {

    private String info = "test4 info";
}

为了减少代码量和版面,我使用了 Lombok


    org.projectlombok
    lombok
    1.18.24
    provided

如果我想使用 Test4 的 info 属性,可以用 if 一直嵌套判断下来:

if (test1 != null) {

    Test2 test2 = test1.getTest2();
    
    if (test2 != null) {
        
        Test3 test3 = test2.getTest3();
        
        if (test3 != null) {
            
            Test4 test4 = test3.getTest4();
            
            if (test4 != null) {

                System.out.println(test4.getInfo());
            }
        }

    }
}
跟着 Guava 学 Java 之 Optional

对象层级一深,代码很臃肿。 Optional 可以帮我们用一行代码解决掉!

String info1 = Optional.ofNullable(test1)
                .map(Test1::getTest2)
                .map(Test2::getTest3)
                .map(Test3::getTest4)
                .map(Test4::getInfo)
                .orElse("hello");

System.out.println(info1);

这行代码达到的效果和上面的 if 一样

解释一下上面几个 Optional 的方法:

上面代码中的 map 方法也可以换作 flatMap 方法,区别是 :flatMap 要求返回值为 Optional 类型,而 map 不需要,flatMap 不会多层包装,map 返回会再次包装 Optional。

我们这里 Test 类是普通类并没有使用 Optional 包装,如果这么写就可以使用 flatMap:

@Data
public class Test1 {

    private String info = "info1";
    private Optional test2;
}

此外我们还可以在发生空指针的情况下,抛出异常或自定义异常:

 String info2 = Optional.of(test1)
          .map(Test1::getTest2)
          .map(Test2::getTest3)
          .map(Test3::getTest4)
          .map(Test4::getInfo)
          .orElseThrow(() -> new RuntimeException("有空指针异常,对象内容为: " + ToStringBuilder.reflectionToString(test1,new MultilineRecursiveToStringStyle())));

System.out.println(info2);

可能你注意到了,我这里的异常输出中用到了 ToStringBuilder 这个类,这个是 Apache Commons Lang3 的库类

 
      org.apache.commons
      commons-lang3
      3.12.0
  

使用它的原因是:我们利用 Optional 一行代码就可以判断很多空指针是不错,但最后就算能捕捉异常也不能确定到底是哪个对象的哪个属性为空,如果只是笼统的给出顶层对象的异常信息,对于排错还是不很直观。当然如果要非常细致地判断和打印日志又会加大代码量,所以想了个折中的办法:将对象的信息递归地打印出来,这样是不是空在排查的时候就一目了然了。 ToStringBuilder.reflectionToString方法可以帮我做到。

ToStringBuilder.reflectionToString(test1, new MultilineRecursiveToStringStyle())

MultilineRecursiveToStringStyle 也可替换为RecursiveToStringStyle,只是不同的显示 Style 罢了

如果我只有 test1 对象不为空,剩下的都为空,那么打印结果如下:

com.xiaobox.gauva.test.Test1@2a5ca609[
  info=info1,
  test2=
]

如果我的 test1 和 test2 对象都不为空,那么打印结果如下:

com.xiaobox.gauva.test.Test1@2a5ca609[
  info=info1,
  test2=com.xiaobox.gauva.test.Test2@26be92ad[
    info=,
    test3=
  ]
]

这样的话,我就可以把信息合并到异常信息中,在排查问题时可以借助这些信息快速定位到哪个对象或哪个属性为空了。

注意: 上面的例子中抛出了异常,但因为不是受检异常,所以 IDE 并没有提示我进行捕捉,写代码有些时候忘了捕获异常,所以,请记得它是将异常 throws 出去了。处理异常的时候别忘了。

跟着 Guava 学 Java 之 Optional

比如一般我们可以这样

private static void opExceptionMethod() throws Exception {
    String info2 = Optional.of(test1)
            .map(Test1::getTest2)
            .map(Test2::getTest3)
            .map(Test3::getTest4)
            .map(Test4::getInfo)
            .orElseThrow(() -> new RuntimeException("有空指针异常,对象内容为: " + ToStringBuilder.reflectionToString(test1, new MultilineRecursiveToStringStyle())));

    System.out.println(info2);
}

显示地抛出,调用者就必须要捕获处理了。 当然也可以不抛出,直接在代码块 try catch

防御性编程

使用 Optional 除了赋予 null 语义,增加了可读性,最大的优点在于它是一种傻瓜式的防护。Optional 迫使你积极思考引用缺失的情况,因为你必须显式地从 Optional 获取引用。直接使用 null 很容易让人忘掉某些情形,尽管 FindBugs 可以帮助查找 null 相关的问题,但是我们还是认为它并不能准确地定位问题根源。

如同输入参数,方法的返回值也可能是 null。和其他人一样,你绝对很可能会忘记别人写的方法 method(a,b) 会返回一个 null,就好像当你实现 method(a,b) 时,也很可能忘记输入参数 a 可以为 null。将方法的返回类型指定为 Optional,也可以迫使调用者思考返回的引用缺失的情形。

关于 null 的建议

其他

从 Spring 5 开始,可以使用 null 安全注解来帮助编写更安全的代码。 此功能称为“空安全性”,这是一组注解,其作用类似于监视潜在的空引用的安全措施。

空安全功能不是让摆脱不安全的代码,而是在编译时生成警告。 这样的警告可以防止在运行时发生灾难性的空指针

注意这些注解只会发出警告,由于有了这个提示,可以提前发现问题,并能够采取适当的措施来避免运行时失败,也就是说你还是可以传递 null 值进来 。

看一个例子:

这是 Spring 框架中 Spring-Core 的 package-info文件内容

路径为:/org/springframework/spring-core/5.2.15.RELEASE/spring-core-5.2.15.RELEASE-sources.jar!/org/springframework/core/package-info.java

@NonNullApi
@NonNullFields
package org.springframework.core;

import org.springframework.lang.NonNullApi;
import org.springframework.lang.NonNullFields;

参考

展开阅读全文

页面更新:2024-03-01

标签:嵌套   注解   字段   指针   属性   异常   对象   参数   代码   方法

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号

Top