参数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