對空值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