參數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