NullPointerException 是 Java 代码中最常见的异常,我将其最可能出现的场景归为以下 5 种:
• 参数值是 Integer 等包装类型,使用时因为自动拆箱出现了空指针异常;
• 字符串比较出现空指针异常;
• 诸如 ConcurrentHashMap 这样的容器不支持 Key 和 Value 为 null,强行 put null 的 Key 或 Value 会出现空指针异常;
• A 对象包含了 B,在通过 A 对象的字段获得 B 之后,没有对字段判空就级联调用 B 的方法出现空指针异常;
• 方法或远程服务返回的 List 不是空而是 null,没有进行判空就直接调用 List 的方法出现空指针异常。
解决方法:
• 对于 Integer 的判空,可以使用 Optional.ofNullable 来构造一个 Optional,然后使用 orElse(0) 把 null 替换为默认值再进行 +1 操作。
• 对于 String 和字面量的比较,可以把字面量放在前面,比如"OK".equals(s),这样即使 s 是 null 也不会出现空指针异常;而对于两个可能为 null 的字符 串变量的 equals 比较,可以使用 Objects.equals,它会做判空处理。
• 对于 rightMethod 返回的 List,由于不能确认其是否为 null,所以在调用 size 方法获得列表大小之前,同样可以使用 Optional.ofNullable 包装一下返回 值,然后通过.orElse(Collections.emptyList()) 实现在 List 为 null 的时候获得一个空的 List,最后再调用 size 方法。
• 对于 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 的话一行代码就够了。
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();}
@Data
public class UserDto {private Long id;private Optional<String> name;private Optional<Integer> age;;@Data@Entity@DynamicUpdatepublic class UserEntity {@Id@GeneratedValue(strategy = IDENTITY)private Long id;@Column(nullable = false)private String name;@Column(nullable = false)private String nickname;@Column(nullable = false)private Integer age;@Column(nullable = false, columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")private Date createDate;}@PostMapping("right")public UserEntity right(@RequestBody UserDto user) {if (user == null || user.getId() == null)throw new IllegalArgumentException("用户Id不能为空");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);}