消灭空指针,Java 8 给我们更好的解决方案

前言

大家好,我是小郭。

在平时的业务开发中,空指针是我们经常遇到的问题,

他可能会导致我们的流程无法正常进行或者一些意外情况的发生。

这就是我们需要避免空指针的原因,那我们有哪些方式去解决这个问题呢?

空指针场景

  1. 包装类型字段,因为自动拆箱出现空指针;
  2. A对象包含B对象,通过A对象获取B对象字段时,没有判断就直接去调用B对象中的方法出现空指针;
  3. 字符串比较,null.equal("字符串")出现空指针
  4. 远程返回的List不是空数组而是null,对其进行操作出现空指针。

线上空指针问题如何排查

日常的开发过程中,一般情况下我们都是通过查看日志来排查空指针的问题,如果日志没有做到位的情况下,我们只能通过NullPointerException抛出的位置去跟踪代码。

这就要求我们,在写代码的时候做好日志的打印

  1. 调用方法的入口进行入参的打印,方法返回的结果进行出参打印

如果没有提前做好日志打印,那我们可以考虑利用阿里的Java诊断工具Arthas来处理

  1. Arthas启动后,获取来了JVM进程
  2. 通过watch指令来监测方法的入参情况

思考

对于这个问题,我总结了一些我在工作中使用到的方法,

最直接的操作都是从根源上消灭出现空指针的可能性,进行先判空再操作。

下面拿商品信息作为一个例子,我们要得到他店铺的名称,你会怎么写

@Data
public class ProductVO implements Serializable {
    @ApiModelProperty("skuId")
    private Long skuId;
    @ApiModelProperty("商品名称")
    private String name;
    @ApiModelProperty("品牌名")
    private String brandName;
    @ApiModelProperty("库存")
    private Integer quantity;
    @ApiModelProperty("小图列表")
    private List<String> smallImgUrls;
    @ApiModelProperty("店铺信息")
    private ShopVO shop;
复制代码
  1. 防御性检查,每一个变量都做一次null检查,每一次不确定一个变量是否为null时,都需要添加一个嵌套的if块,这增加了代码的层数。
// 获取店铺名称
private String getShopName1(ProductVO productVO){
    if (productVO != null){
        ShopVO shop = productVO.getShopVO();
        if (shop != null){
            return shop.getName();
        }
    }
    return "";
}
复制代码
  1. 快速失败检查,每一个null检查都是一个退出点,都返回一个固定的字符串,但是不能避免的是,忘记对某一个变量的检查。
// 获取店铺名称
private String getShopName2(ProductVO productVO){
    String result = "";
    if (Objects.isNull(productVO)){
        return result;
    }
    ShopVO shop = productVO.getShopVO();
    if (Objects.isNull(shop)){
        return result;
    }
    return shop.getName();
}
复制代码
  1. 人为控制,对数据进行严格的控制,不能存在非空字段,但是不能很难保证所有数据都是正常的数据

  2. 利用Java8中的optional来做控制,对缺失的值建模,变量存在时对类进行简单的封装,不存在时,缺失的值会被建模成一个空的Optional对象

private static String getName4(ProductVO productVO){
    return Optional.ofNullable(productVO).flatMap(data -> Optional.ofNullable(shopVO.getShopVO()))
            .map(ShopVO::getName).orElse("");
}
复制代码
  1. 创建一个Optional封装的ProductVO对象

  2. 将Optional转换为Optional

  3. 利用map,将Optianal转换为Optional

  4. 调用链上的任何一个方法,返回一个空,那么结果就是我们设置的默认值

上面的几种方法中,我们看到了第四种方法,只用了一行代码就帮我们实现了消灭空指针的动作,但是Java 8中的 Optional 需要结合多个的方法来使用他,现在网上已经有很多详细方法文章,就不再做过多的介绍。

注意flatMap 与 Map 的区别

对于Stream流的运算中,flatMap 与 Map是我们常使用到的方法,很多人都没有搞清楚他们的差别是什么。

map(): map对列表中的每个元素应用一个函数,返回应用后的元素所组成的列表。

flatMap(): flatMap是一种常用的组合子,结合映射[mapping]和扁平化[flattening]。 flatMap需要一个处理嵌套列表的函数,然后将结果串连起来。

举个例子,一眼看出他们的区别

List<String> list = Arrays.asList("北京 天安门", "上海 东方明珠", "厦门 鼓浪屿"); 
//flatMap方法 
list.stream().flatMap(item -> Arrays.stream(item.split(" "))).collect(Collectors.toList()).forEach(System.out::println);
//结果: 北京 天安门 上海 东方明珠 厦门 鼓浪屿 
// Map方法 
list.stream().map(item -> Stream.of(item.split(" "))).forEach(System.out::println); 
// java.util.stream.ReferencePipeline$Head@6576fe71 
// java.util.stream.ReferencePipeline$Head@76fb509a 
// java.util.stream.ReferencePipeline$Head@300ffa5d
复制代码

我们可以看到他们结果差异是非常大的

Map方法将list转换为了三个小的List对象的结果集

FlatMap方法的操作就是比Map方法的基础上,多做了一个扁平化[flattening]操作,将结果转化成一级结构,将里面的结果都取出来。

实践

List<ProductRespDTO> list = productVOList.getList().stream().map(d -> {
    return ProductRespDTO.builder().drugEncode(d.getSkuId()).drugName(d.getName())
        .price(BigDecimal.valueOf(d.getPrice()).divide(new BigDecimal("100"))).usage(d.getUseMethod())
        .imgUrl(d.getSmallImgUrls().stream().findFirst().orElse(null)).build();
复制代码

这个例子中是否会存在空指针问题呢,如果是你你会怎么去修改?

总结

对于空指针问题,看起来问题不大,但是影响到了线上的业务正常运转,那肯定是不行的。

我们一定要有很清晰的思路去解决这个问题

  1. 事前,

    • 一定要做好日志的打印工作,为了更方便的排查问题;
    • 在实现业务逻辑的时候,如果你对你操作的对象不是很确定,那一定要先判空后操作;
    • 针对于字符串类型的空指针我们可以采用Objects来做对比;
    • 必填字段的入参校验
  2. 事中,事情既然已经发生了,那需要我们快速的通过日志和Arthas工具来定位问题,快速修复上线减少故障发生的时间:

  3. 事后,我们可以加强code review来审查自己的代码,避免这类情况的再次发生。

大家可以分享一下,自己平时遇到空指针是如何处理的~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的 Java 实现: ```java import java.util.*; public class DestroyTheStars { public static void main(String[] args) { // 创建一个 10x10 的星星矩阵 boolean[][] stars = new boolean[10][10]; // 初始化星星矩阵,随机生成一些星星 Random rand = new Random(); for (int i = 0; i < 10; i++) { for (int j = 0; j < 10; j++) { stars[i][j] = rand.nextBoolean(); } } // 输出初始的星星矩阵 System.out.println("初始的星星矩阵:"); for (int i = 0; i < 10; i++) { for (int j = 0; j < 10; j++) { System.out.print(stars[i][j] ? "*" : "."); } System.out.println(); } // 输入要消灭的星星坐标 Scanner scanner = new Scanner(System.in); System.out.print("请输入要消灭的星星坐标(格式:行 列):"); int row = scanner.nextInt(); int col = scanner.nextInt(); // 检查输入坐标是否合法 if (row < 0 || row >= 10 || col < 0 || col >= 10) { System.out.println("输入坐标有误!"); return; } // 消灭星星 destroy(stars, row, col); // 输出消灭后的星星矩阵 System.out.println("消灭后的星星矩阵:"); for (int i = 0; i < 10; i++) { for (int j = 0; j < 10; j++) { System.out.print(stars[i][j] ? "*" : "."); } System.out.println(); } } // 消灭星星的方法 private static void destroy(boolean[][] stars, int row, int col) { if (!stars[row][col]) { return; } stars[row][col] = false; if (row > 0) { destroy(stars, row - 1, col); } if (row < 9) { destroy(stars, row + 1, col); } if (col > 0) { destroy(stars, row, col - 1); } if (col < 9) { destroy(stars, row, col + 1); } } } ``` 运行程序,输入要消灭的星星坐标(行和列,从 0 开始),程序会输出消灭后的星星矩阵。程序使用递归算法实现星星消灭,从输入的坐标开始,将该位置的星星消灭,并递归消灭其周围的星星,直到所有相连的星星都被消灭

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值