对空值null的处理问题


参数Integer包装类型,自动拆箱出现空指针异常

使用Optional.ofNullable构造一个Optional,然后使用orElse(0)把null替换位默认值再进行+1操作

字符串比较出现空指针异常

对于String和字面量的比较,把字面量放前边,对于两个可能为null的字符串比较,可以使用Objects.equals;
//Objects.equals(a,b);
public static boolean equals(Object a, Object b) { return (a == b) || (a != null && a.equals(b)); }

ConcurrentHashMap强行put null的key或value出现空指针异常

由于ConcurrentHashMap的key和value都不支持null,解决方式即不要把null存进去.注意HashMap的key和value可以存入null。

级联调空对象的方法出现空指针异常

级联调用,需要判空的地方可能会很多,如果用if-else判空代码多,可用Optional一行解决;
List<Person> personList = new ArrayList<>();
Optional.ofNullable(personList).orElseGet(() -> {
        log.info("personList为null!");//打印为空时的日志
        return new ArrayList<>();//返回空list
}).stream().filter(Objects::nonNull).forEach(person -> {//过滤掉为空的对象
        log.info(person.getName());
});
//其中orElseGet(() -> {}表示,如果personList为null执行{}括号中的逻辑

调用方法或远程服务返回的List不是空而是null出现空指针异常

对于不能确认返回的list是否为null,可以使用Optional.ofNullable包装一下返回值,然后通过orElse(Collections。emptyList())实现在List为空的时候获得一个空的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();
}

POJO中属性的null处理

  • 对于JSON到DTO的反序列化过程,null的表达时有歧义的

//用户更新操作,不传意味着客户端不需要更新这个属性,维持数据库原先的值;传null,意味着客户端希望重置这个属性;
解决:使用 Optional 来包装,区分客户端不穿数据还是故意传null
如果不传值,那么Optional本身为null,直接跳过Entity字段的更新即可,动态sql不包含该列;
如果传值,那么进一步判断传的是不是null,姓名传null是希望把该值置空,允许这样操作使用Optional的orElse方法一键把空转换为空字符串即可。
年龄传null,不存在重置操作,可以使用Optional的orElseThrow方法的值为空的时候抛出IllegalArgumentException
昵称传null,因为数据库中姓名不可能为null,可以把昵称设置为guest加上数据库取出来的姓名
  • 字段有默认值

//如果客户端不传值,就会赋值为默认值,导致创建时间也被更新的到了数据库中;
在 字段上使用 @Column 注解,把数据库字段  NOT NULL,并设置 createDate 的默认值为 CURRENT_TIMESTAMP,由数据库来生成创建时间。
@Column(nullable = false) 
private String name;
  • 字符串格式化把null格式化为null字符串

如果只是进行简单的字符串格式化存入数据将变成guestnull,所以需要进行判断;
  • DTO和Entity公用一个POJO

对于某些字段,不应该把他们暴露在DTO中,否则很容易把客户端随意设置的值更新到数据库中;
所以,对DTO和Entity进行拆分,如Dto中区分客户端不传数据还是故意传null;Entity中字段上使用@Column注解等;
  • 数据库字段允许保存null,会进一步增加出错的可能性和复杂度

如果数据支持null的话,可能有null、空字符串、和字符串null三种状态,所以所有属性都有默认值会简单一点;
  • 参数校验:

@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());//避免了数据为空的情况,如:“尊敬的 null 你好,XXX”
    if (user.getAge() != null) {
        userEntity.setAge(user.getAge().orElseThrow(() -> new IllegalArgumentException("年龄不能为空")));
    }
    return userEntityRepository.save(userEntity);
}

mysql中null的注意事项

1. mysql中sum函数没统计到任何记录时,会返回null而不是0,可以使用IFNULL函数把null转换为0;
2. mysql中count字段不统计null值,count(*)才会统计所有记录;
3. mysql中使用=、<、>、这样的算数操作符比较null的几个总是null,这种比较就显得没有任何意义,需要使用is null、is not null或isnull()函数来比较;

优化指南

1. 订阅空指针异常报警;
2. 针对于空的情况,需要考虑应该抛出异常、设置默认值、记录日志等。

 

原文链接:https://time.geekbang.org/column/article/216830


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM