Java中怎樣處理空指針異常
程序中的變量是 null,就意味著它沒(méi)有引用指向或者說(shuō)沒(méi)有指針。這時(shí),我們對(duì)這個(gè)變量進(jìn)行任何操作,都必然會(huì)引發(fā)空指針異常,在 Java 中就是 NullPointerException。那么,空指針異常容易在哪些情況下出現(xiàn),又應(yīng)該如何修復(fù)呢?
空指針異常雖然惱人但好在容易定位,更麻煩的是要弄清楚 null 的含義。比如,客戶端給服務(wù)端的一個(gè)數(shù)據(jù)是 null,那么其意圖到底是給一個(gè)空值,還是沒(méi)提供值呢?再比如,數(shù)據(jù)庫(kù)中字段的 NULL 值,是否有特殊的含義呢,針對(duì)數(shù)據(jù)庫(kù)中的 NULL 值,寫 SQL 需要特別注意什么呢?
今天,就讓我們帶著這些問(wèn)題開(kāi)始 null 的踩坑之旅吧。
NullPointerException 是 Java 代碼中最常見(jiàn)的異常,我將其最可能出現(xiàn)的場(chǎng)景歸為以下 5 種:
- 參數(shù)值是 Integer 等包裝類型,使用時(shí)因?yàn)樽詣?dòng)拆箱出現(xiàn)了空指針異常;
- 字符串比較出現(xiàn)空指針異常;
- 諸如 ConcurrentHashMap 這樣的容器不支持 Key 和 Value 為 null,強(qiáng)行 put null 的 Key 或 Value 會(huì)出現(xiàn)空指針異常;
- A 對(duì)象包含了 B,在通過(guò) A 對(duì)象的字段獲得 B 之后,沒(méi)有對(duì)字段判空就級(jí)聯(lián)調(diào)用 B 的方法出現(xiàn)空指針異常;方法或遠(yuǎn)程服務(wù)返回的 List 不是空而是 null,沒(méi)有進(jìn)行判空就直接調(diào)用 List 的方法出現(xiàn)空指針異常。
private List<String> wrongMethod(FooService fooService, Integer i, String s, String t) { log.info("result {} {} {} {}", i + 1, s.equals("OK"), s.equals(t), new ConcurrentHashMap<String, String>().put(null, null)); if (fooService.getBarService().bar().equals("OK")) log.info("OK"); return null; } @GetMapping("wrong") public int wrong(@RequestParam(value = "test", defaultValue = "1111") String test) { return wrongMethod(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").size(); } class FooService { @Getter private BarService barService; } class BarService { String bar() { return "OK"; } }
修復(fù)思路如下:
對(duì)于 Integer 的判空,可以使用 Optional.ofNullable 來(lái)構(gòu)造一個(gè) Optional,然后使用 orElse(0) 把 null 替換為默認(rèn)值再進(jìn)行 +1 操作。對(duì)于 String 和字面量的比較,可以把字面量放在前面,比如"OK".equals(s),這樣即使 s 是 null 也不會(huì)出現(xiàn)空指針異常;而對(duì)于兩個(gè)可能為 null 的字符串變量的 equals 比較,可以使用 Objects.equals,它會(huì)做判空處理。
對(duì)于 ConcurrentHashMap,既然其 Key 和 Value 都不支持 null,修復(fù)方式就是不要把 null 存進(jìn)去。HashMap 的 Key 和 Value 可以存入 null,而 ConcurrentHashMap 看似是 HashMap 的線程安全版本,卻不支持 null 值的 Key 和 Value,這是容易產(chǎn)生誤區(qū)的一個(gè)地方。
對(duì)于類似 fooService.getBarService().bar().equals(“OK”) 的級(jí)聯(lián)調(diào)用,需要判空的地方有很多,包括 fooService、getBarService() 方法的返回值,以及 bar 方法返回的字符串。如果使用 if-else 來(lái)判空的話可能需要好幾行代碼,但使用 Optional 的話一行代碼就夠了。
對(duì)于 rightMethod 返回的 List,由于不能確認(rèn)其是否為 null,所以在調(diào)用 size 方法獲得列表大小之前,同樣可以使用 Optional.ofNullable 包裝一下返回值,然后通過(guò).orElse(Collections.emptyList()) 實(shí)現(xiàn)在 List 為 null 的時(shí)候獲得一個(gè)空的 List,最后再調(diào)用 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(); }
我們根據(jù)業(yè)務(wù)需要分別對(duì)姓名、年齡和昵稱進(jìn)行更新:對(duì)于姓名,我們認(rèn)為客戶端傳 null 是希望把姓名重置為空,允許這樣的操作,使用 Optional 的 orElse 方法一鍵把空轉(zhuǎn)換為空字符串即可。
對(duì)于年齡,我們認(rèn)為如果客戶端希望更新年齡就必須傳一個(gè)有效的年齡,年齡不存在重置操作,可以使用 Optional 的 orElseThrow 方法在值為空的時(shí)候拋出 IllegalArgumentException。
對(duì)于昵稱,因?yàn)閿?shù)據(jù)庫(kù)中姓名不可能為 null,所以可以放心地把昵稱設(shè)置為 guest 加上數(shù)據(jù)庫(kù)取出來(lái)的姓名。
@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()); if (user.getAge() != null) { userEntity.setAge(user.getAge().orElseThrow(() -> new IllegalArgumentException("年齡不能為空"))); } return userEntityRepository.save(userEntity); }
到此這篇關(guān)于Java中怎樣處理空指針異常的文章就介紹到這了,更多相關(guān)Java 空指針異常內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決SpringBoot配置文件項(xiàng)目重啟出現(xiàn)亂碼的問(wèn)題
最近在創(chuàng)建了SpringBoot項(xiàng)目后往配置文件中寫了相關(guān)的系統(tǒng)配置,并且在上面加了中文注釋,但是在重啟項(xiàng)目或開(kāi)機(jī)重啟后遇到了注釋亂碼的情況,下面這篇文章主要給大家介紹一下如何解決SpringBoot配置文件項(xiàng)目重啟出現(xiàn)亂碼的問(wèn)題,需要的朋友可以參考下2023-06-06JAVA中常用的設(shè)計(jì)模式:?jiǎn)卫J?,工廠模式,觀察者模式
設(shè)計(jì)模式(Design pattern)代表了最佳的實(shí)踐,通常被有經(jīng)驗(yàn)的面向?qū)ο蟮能浖_(kāi)發(fā)人員所采用。設(shè)計(jì)模式是軟件開(kāi)發(fā)人員在軟件開(kāi)發(fā)過(guò)程中面臨的一般問(wèn)題的解決方案。這些解決方案是眾多軟件開(kāi)發(fā)人員經(jīng)過(guò)相當(dāng)長(zhǎng)的一段時(shí)間的試驗(yàn)和錯(cuò)誤總結(jié)出來(lái)的。2020-04-04Java利用線程工廠監(jiān)控線程池的實(shí)現(xiàn)示例
這篇文章主要介紹了Java利用線程工廠監(jiān)控線程池的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04java中字符串如何計(jì)算字節(jié)長(zhǎng)度
這篇文章主要介紹了java中字符串如何計(jì)算字節(jié)長(zhǎng)度,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10Mybatis執(zhí)行Update返回行數(shù)為負(fù)數(shù)的問(wèn)題
這篇文章主要介紹了Mybatis執(zhí)行Update返回行數(shù)為負(fù)數(shù)的問(wèn)題,具有很好的參考價(jià)值,希望大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12使用logstash同步mysql數(shù)據(jù)到elasticsearch實(shí)現(xiàn)
這篇文章主要為大家介紹了使用logstash同步mysql數(shù)據(jù)到elasticsearch實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12java實(shí)現(xiàn)微信公眾號(hào)掃一掃
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)微信公眾號(hào)掃一掃,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-04-04SpringBoot集成itextpdf實(shí)現(xiàn)根據(jù)模板動(dòng)態(tài)生成PDF
這篇文章主要為大家詳細(xì)介紹了SpringBoot如何集成itextpdf實(shí)現(xiàn)根據(jù)模板動(dòng)態(tài)生成PDF,文中的示例代碼講解詳細(xì),需要的小伙伴可以參考一下2024-03-03