Java中怎样处理空指针异常

 更新时间:2022年05月29日 15:09:08   作者:程序员说书  
程序中的变量是 null,就意味着它没有引用指向或者说没有指针。这时,我们对这个变量进行任何操作,都必然会引发空指针异常,本文主要介绍了Java中怎样处理空指针异常,感兴趣的可以了解一下

程序中的变量是 null,就意味着它没有引用指向或者说没有指针。这时,我们对这个变量进行任何操作,都必然会引发空指针异常,在 Java 中就是 NullPointerException。那么,空指针异常容易在哪些情况下出现,又应该如何修复呢?

空指针异常虽然恼人但好在容易定位,更麻烦的是要弄清楚 null 的含义。比如,客户端给服务端的一个数据是 null,那么其意图到底是给一个空值,还是没提供值呢?再比如,数据库中字段的 NULL 值,是否有特殊的含义呢,针对数据库中的 NULL 值,写 SQL 需要特别注意什么呢?

今天,就让我们带着这些问题开始 null 的踩坑之旅吧。

NullPointerException 是 Java 代码中最常见的异常,我将其最可能出现的场景归为以下 5 种:

  • 参数值是 Integer 等包装类型,使用时因为自动拆箱出现了空指针异常;
  • 字符串比较出现空指针异常;
  • 诸如 ConcurrentHashMap 这样的容器不支持 Key 和 Value 为 null,强行 put null 的 Key 或 Value 会出现空指针异常;
  • A 对象包含了 B,在通过 A 对象的字段获得 B 之后,没有对字段判空就级联调用 B 的方法出现空指针异常;方法或远程服务返回的 List 不是空而是 null,没有进行判空就直接调用 List 的方法出现空指针异常。
private List<String> wrongMethod(FooService fooService, Integer i, String s, String t) {
    log.info("result {} {} {} {}", i + 1, s.equals("OK"), s.equals(t),
            new ConcurrentHashMap<String, String>().put(null, null));
    if (fooService.getBarService().bar().equals("OK"))
        log.info("OK");
    return null;
}

@GetMapping("wrong")
public int wrong(@RequestParam(value = "test", defaultValue = "1111") String test) {
    return wrongMethod(test.charAt(0) == '1' ? null : new FooService(),
            test.charAt(1) == '1' ? null : 1,
            test.charAt(2) == '1' ? null : "OK",
            test.charAt(3) == '1' ? null : "OK").size();
}

class FooService {
    @Getter
    private BarService barService;

}

class BarService {
    String bar() {
        return "OK";
    }
}

修复思路如下:

  • 对于 Integer 的判空,可以使用 Optional.ofNullable 来构造一个 Optional,然后使用 orElse(0) 把 null 替换为默认值再进行 +1 操作。对于 String 和字面量的比较,可以把字面量放在前面,比如"OK".equals(s),这样即使 s 是 null 也不会出现空指针异常;而对于两个可能为 null 的字符串变量的 equals 比较,可以使用 Objects.equals,它会做判空处理。

  • 对于 ConcurrentHashMap,既然其 Key 和 Value 都不支持 null,修复方式就是不要把 null 存进去。HashMap 的 Key 和 Value 可以存入 null,而 ConcurrentHashMap 看似是 HashMap 的线程安全版本,却不支持 null 值的 Key 和 Value,这是容易产生误区的一个地方。

  • 对于类似 fooService.getBarService().bar().equals(“OK”) 的级联调用,需要判空的地方有很多,包括 fooService、getBarService() 方法的返回值,以及 bar 方法返回的字符串。如果使用 if-else 来判空的话可能需要好几行代码,但使用 Optional 的话一行代码就够了。

  • 对于 rightMethod 返回的 List,由于不能确认其是否为 null,所以在调用 size 方法获得列表大小之前,同样可以使用 Optional.ofNullable 包装一下返回值,然后通过.orElse(Collections.emptyList()) 实现在 List 为 null 的时候获得一个空的 List,最后再调用 size 方法。

private List<String> rightMethod(FooService fooService, Integer i, String s, String t) {
    log.info("result {} {} {} {}", Optional.ofNullable(i).orElse(0) + 1, "OK".equals(s), Objects.equals(s, t), new HashMap<String, String>().put(null, null));
    Optional.ofNullable(fooService)
            .map(FooService::getBarService)
            .filter(barService -> "OK".equals(barService.bar()))
            .ifPresent(result -> log.info("OK"));
    return new ArrayList<>();
}

@GetMapping("right")
public int right(@RequestParam(value = "test", defaultValue = "1111") String test) {
    return Optional.ofNullable(rightMethod(test.charAt(0) == '1' ? null : new FooService(),
            test.charAt(1) == '1' ? null : 1,
            test.charAt(2) == '1' ? null : "OK",
            test.charAt(3) == '1' ? null : "OK"))
            .orElse(Collections.emptyList()).size();
}
  • 我们根据业务需要分别对姓名、年龄和昵称进行更新:对于姓名,我们认为客户端传 null 是希望把姓名重置为空,允许这样的操作,使用 Optional 的 orElse 方法一键把空转换为空字符串即可。

  • 对于年龄,我们认为如果客户端希望更新年龄就必须传一个有效的年龄,年龄不存在重置操作,可以使用 Optional 的 orElseThrow 方法在值为空的时候抛出 IllegalArgumentException。

  • 对于昵称,因为数据库中姓名不可能为 null,所以可以放心地把昵称设置为 guest 加上数据库取出来的姓名。

@PostMapping("right")
public UserEntity right(@RequestBody UserDto user) {
    if (user == null || user.getId() == null)
        throw new IllegalArgumentException("用户Id不能为空");

    UserEntity userEntity = userEntityRepository.findById(user.getId())
            .orElseThrow(() -> new IllegalArgumentException("用户不存在"));

    if (user.getName() != null) {
        userEntity.setName(user.getName().orElse(""));
    }
    userEntity.setNickname("guest" + userEntity.getName());
    if (user.getAge() != null) {
        userEntity.setAge(user.getAge().orElseThrow(() -> new IllegalArgumentException("年龄不能为空")));
    }
    return userEntityRepository.save(userEntity);
}

到此这篇关于Java中怎样处理空指针异常的文章就介绍到这了,更多相关Java 空指针异常内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 如何使用MybatisPlus快速进行增删改查详解

    如何使用MybatisPlus快速进行增删改查详解

    增删改查在日常开发中是再正常不多的一个需求了,下面这篇文章主要给大家介绍了关于如何使用MybatisPlus快速进行增删改查的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-08-08
  • Springboot整合minio实现文件服务的教程详解

    Springboot整合minio实现文件服务的教程详解

    这篇文章主要介绍了Springboot整合minio实现文件服务的教程,文中的示例代码讲解详细,对我们的工作或学习有一定帮助,需要的可以参考一下
    2022-06-06
  • Java 实现常见的非对称加密算法

    Java 实现常见的非对称加密算法

    这篇文章主要介绍了Java 实现常见的非对称加密算法,帮助大家更好的理解和使用Java,感兴趣的朋友可以了解下
    2020-11-11
  • 解决Springboot中Feignclient调用时版本问题

    解决Springboot中Feignclient调用时版本问题

    这篇文章主要介绍了解决Springboot中Feign client调用时版本问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • Java中synchronized关键字引出的多种锁 问题

    Java中synchronized关键字引出的多种锁 问题

    synchronized关键字是JAVA中常用的同步功能,提供了简单易用的锁功能。这篇文章主要介绍了Java中synchronized关键字引出的多种锁问题,需要的朋友可以参考下
    2019-07-07
  • Spring Boot中使用RabbitMQ 生产消息和消费消息的实例代码

    Spring Boot中使用RabbitMQ 生产消息和消费消息的实例代码

    本文介绍了在SpringBoot中如何使用RabbitMQ进行消息的生产和消费,详细阐述了RabbitMQ中交换机的作用和类型,包括直连交换机、主题交换机、扇出交换机和头交换机,并解释了各自的消息路由机制,感兴趣的朋友一起看看吧
    2024-10-10
  • springboot上传图片文件步骤详解

    springboot上传图片文件步骤详解

    这篇文章主要介绍了springboot上传图片文件步骤详解,本文通过实例代码图文相结合给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-12-12
  • Spring Boot中使用Spring-data-jpa的配置方法详解

    Spring Boot中使用Spring-data-jpa的配置方法详解

    今天小编就为大家分享一篇关于Spring Boot中使用Spring-data-jpa的配置方法详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-03-03
  • java多线程并发executorservice(任务调度)类

    java多线程并发executorservice(任务调度)类

    这篇文章主要介绍了线程并发ScheduledExecutorService类,设置 ScheduledExecutorService ,2秒后,在 1 分钟内每 10 秒钟蜂鸣一次
    2014-01-01
  • Java中的Comparable和Comparator接口

    Java中的Comparable和Comparator接口

    这篇文章主要介绍了Java中的Comparable和Comparator接口,文章围绕主题展开详细的内容戒杀,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-09-09

最新评论