淺析Java如何優(yōu)雅的避免那無處不在的空指針異常
在Java編程語(yǔ)言中,NullPointerException
(簡(jiǎn)稱NPE)是一種常見的運(yùn)行時(shí)異常,當(dāng)程序試圖訪問或操作一個(gè)還未初始化(即值為null)的對(duì)象引用時(shí),Java虛擬機(jī)就會(huì)拋出NullPointerException
。如果我們?cè)谌粘i_發(fā)中,不能很好的去規(guī)避NPE,那么可能因?yàn)閿?shù)據(jù)或者其他問題就會(huì)導(dǎo)致線上問題。。。很煩。。。。
阿里巴巴開發(fā)手冊(cè)規(guī)約中也說明防止NPE,是程序員的基本素養(yǎng)。。。
接下來我們先談?wù)剮追N可能會(huì)出現(xiàn)空指針異常的方式。
出現(xiàn)空指針異常的情況
訪問空對(duì)象的屬性或調(diào)用空對(duì)象的方法當(dāng)一個(gè)對(duì)象是null時(shí),試圖訪問一個(gè)對(duì)象的屬性或調(diào)用其方法,就會(huì)觸發(fā)空指針異常。
String text = null; int length = text.length(); User user = null; String userName = user.getUserName();
數(shù)組為null或者數(shù)組元素為null當(dāng)嘗試訪問數(shù)組中的某個(gè)索引處的元素,而該元素為null
時(shí),同樣會(huì)導(dǎo)致空指針異常。
String[] strs = null; int length = strs.length; String[] strs = new String[3]; int length = strs[2].length();
集合中null元素訪問當(dāng)集合中存在null元素,當(dāng)我們遍歷集合,訪問到這個(gè)元素的屬性或者方法時(shí)也會(huì)拋出NPE,這種情況也會(huì)出現(xiàn)在我們的日常開發(fā)中,有時(shí)候就會(huì)因?yàn)閿?shù)據(jù)問題導(dǎo)致這種情況發(fā)生,常常也莫名其妙。。。。
List<String> list = Lists.newArrayList(); list.add(null); System.out.println(list.get(0).length());
調(diào)用的方法返回null調(diào)用某個(gè)方法,期望其返回一個(gè)非null的對(duì)象,但實(shí)際返回了null。當(dāng)然這種情況等同于訪問空對(duì)象的屬性或者方法。這在實(shí)際開發(fā)過程中極易出現(xiàn)的一種情況。比如我們使用Mybatis
從數(shù)據(jù)庫(kù)中查詢一條記錄時(shí),數(shù)據(jù)不存在,就會(huì)返回null。這種情況尤為注意。
private User getUserInfo(){ return null; } User user = getUserInfo(); String userName = user.getUserName();
使用基本數(shù)據(jù)類型的包裝類在使用基本數(shù)據(jù)類型的包裝類時(shí),如果未正確初始化,再轉(zhuǎn)成int時(shí),可能導(dǎo)致空指針異常。
Integer i = null; int num = i;
以上大概是我想到或者常遇到的一些可能會(huì)發(fā)生NPE的情況,如果還有其他情況,可以貼出來討論。
那么我們?cè)撊绾伪苊釴PE呢?
避免NPE的幾種方式
訪問對(duì)象前要謹(jǐn)慎在使用對(duì)象之前,始終檢查它是否為null。這包括方法參數(shù)、返回值以及對(duì)象的屬性。在訪問對(duì)象的方法或?qū)傩灾埃褂脳l件語(yǔ)句判斷對(duì)象是否為null。比如我們?cè)谠L問User對(duì)象前,一定要判null
User user = new User(); if (user != null){ String userName = user.getUserName(); Address address = user.getAddress(); if (address != null){ String coutry = address.getCountry(); } }
或者我們的user是從一個(gè)方法中獲取的,例如數(shù)據(jù)庫(kù)中查詢,那么我們?cè)谠L問這個(gè)對(duì)象前,一定要判null,如果為null要拋出對(duì)應(yīng)的業(yè)務(wù)異常,然后我們就可以在接口響應(yīng)中對(duì)應(yīng)返回錯(cuò)誤的信息即可,此時(shí)就算是一個(gè)正常的流程了。這點(diǎn)尤為重要,一定要注意。
User user = userManager.getUserById(Long userId); if (user == null){ throw new ServiceException(""當(dāng)前查詢的對(duì)象不存在); }
關(guān)于SpringBoot項(xiàng)目中捕獲自定義業(yè)務(wù)異常,統(tǒng)一異常管理,統(tǒng)一結(jié)果返回,可以參考這篇文章:SpringBoot統(tǒng)一結(jié)果返回,統(tǒng)一異常處理,大牛都這么玩
當(dāng)然如果使我們?cè)趯?code>User getUserById(Long id)返回對(duì)象或者List<User> listUserByIds(List<Long> idList)
時(shí)我們可以不返回null
,可以返回一個(gè)對(duì)象默認(rèn)信息或者一個(gè)空集合,這樣調(diào)用方就不會(huì)出現(xiàn)NPE風(fēng)險(xiǎn),當(dāng)然我們不強(qiáng)制返回一個(gè)對(duì)象或者空集合,但是必須添加注釋充分 說明什么情況下會(huì)返回null值。這也是阿里巴巴開發(fā)手冊(cè)規(guī)約的建議。
使用Optional類JDK8以上版本提供了Optional
類,它是一個(gè)容器對(duì)象,可用于包裝可能為null的值。我們可以使用它判斷null問題,同時(shí)也解決了多層級(jí)訪問問題,配合使用orElse時(shí),會(huì)先執(zhí)行orElse方法,然后執(zhí)行邏輯代碼,不管是否出現(xiàn)了空指針。
String country = Optional.ofNullable(user) .map(User::getAddress) .map(Address::getCountry) .orElse(""); String country = Optional.ofNullable(user) .map(User::getAddress) .map(Address::getCountry) .orElseGet(() -> defaultContry()); private String defaultContry(){ return "CN"; }
我們還可以使用orElseThrow()方法,當(dāng)Optional中的對(duì)象是一個(gè)null時(shí)我們直接拋出異常:
String userName = Optional.ofNullable(user).map(User::getUserName).orElseThrow(() -> new ServiceException("當(dāng)前用戶信息不存在"));
使用斷言避免空指針使用Java斷言(assert)來檢查變量是否為null。但要注意,斷言通常在開發(fā)和測(cè)試階段啟用,而在生產(chǎn)環(huán)境中可能被禁用(在生產(chǎn)環(huán)境中,通常不會(huì)啟用斷言以避免不必要的性能開銷以及防止?jié)撛诘腻e(cuò)誤信息泄漏)。
User user = new User(); assert user != null : "user should not be null"; Address address = user.getAddress(); assert address != null : "address should not be null"; String coutry = address.getCountry();
使用@Nullable注解使用javax.annotation.Nullable
注解,@Nullable
注解通常用于標(biāo)記一個(gè)方法的參數(shù)、返回值或者字段可能為null。這個(gè)注解并非Java標(biāo)準(zhǔn)庫(kù)的一部分,但在一些第三方庫(kù)(如JSR 305庫(kù)中的javax.annotation.Nullable
,以及Google Guava和JetBrains的Kotlin標(biāo)準(zhǔn)庫(kù)等)中廣泛使用,并且被許多IDE和靜態(tài)分析工具支持。以便在編譯期或開發(fā)工具中提示可能的NPE風(fēng)險(xiǎn)。
@Nullable private static User getUserById(Long userId){ return null; } private static void handlerUser(@Nullable User user){ System.out.println(user.getUserName()); } public static void main(String[] args) { Long userId = 0L; User user = getUserById(userId); String userName = user.getUserName(); handlerUser(user); }
此時(shí)IDEA就會(huì)警告會(huì)出現(xiàn)NPE風(fēng)險(xiǎn)
借助工具掃描代碼在Java開發(fā)中,我們還可以使用以下工具掃描代碼以發(fā)現(xiàn)潛在的空指針異常風(fēng)險(xiǎn)。
IntelliJ IDEA:內(nèi)置了強(qiáng)大的靜態(tài)代碼分析器,能夠檢測(cè)出可能的NPE和其他代碼問題。
SonarQube / SonarLint:提供持續(xù)集成和本地IDE插件形式的靜態(tài)代碼分析,能找出潛在的空指針以及其他質(zhì)量或安全問題。Sonar可以定時(shí)掃描倉(cāng)庫(kù)中的代碼,可以發(fā)現(xiàn)代碼中的一些潛在風(fēng)險(xiǎn),可以通過一些通知例如郵件等告知代碼提交者這段代碼的風(fēng)險(xiǎn)。
FindBugs(現(xiàn)更名為SpotBugs):另一個(gè)開源的靜態(tài)分析工具,能夠發(fā)現(xiàn)潛在的bug,包括可能導(dǎo)致NPE的情況。
阿里巴巴Java開發(fā)規(guī)約插件: 對(duì)于Eclipse和IntelliJ IDEA都有相應(yīng)的插件版本,基于阿里巴巴內(nèi)部Java編碼規(guī)范,包含了對(duì)可能出現(xiàn)NPE情況的檢測(cè)。
補(bǔ)充一點(diǎn)
在JDK 17中引入的Helpful NullPointerExceptions特性確實(shí)增強(qiáng)了空指針異常信息的準(zhǔn)確性與可用性。當(dāng)發(fā)生NullPointerException時(shí),JVM現(xiàn)在能夠提供更精確的位置信息,特別是在鏈?zhǔn)秸{(diào)用場(chǎng)景下,它會(huì)指出導(dǎo)致空指針異常的具體對(duì)象引用。這有助于開發(fā)者更快地定位到代碼中的問題所在,無需通過堆棧跟蹤逐層分析來判斷哪個(gè)對(duì)象引用為null。
假如我們?cè)L問user.getAddress().getCountry().length()
時(shí),在JDK17以前,如果發(fā)生了空指針異常,他只會(huì)打印出來發(fā)生了空指針異常,但是并沒有告知到底是user對(duì)象還是address對(duì)象還是coutnry發(fā)生了異常:
Exception in thread "main" java.lang.NullPointerException
at com.study.base.core.base.NpeTest.main(NpeTest.java:23)
但是在JDK17以后,借助Helpful NullPointerExceptions特性,異常信息將更加精確,可能會(huì)類似打印這樣的信息,精確到那個(gè)值發(fā)生了空指針異常:
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Address.getCountry()" because "user.address" is null
at com.study.base.core.base.NpeTest.main(NpeTest.java:23)
這又多了一個(gè)升級(jí)JDK到17以上的理由。
結(jié)論
NullPointerException(NPE)是Java開發(fā)中常見的運(yùn)行時(shí)異常,源于對(duì)未初始化或已置為null的對(duì)象引用進(jìn)行操作。在實(shí)際開發(fā)過程中,進(jìn)行非空檢查、使用Optional類以及采用Null安全注解以及使用檢查工具等策略可以有效避免此類異常的發(fā)生。
以上就是淺析Java如何優(yōu)雅的避免那無處不在的空指針異常的詳細(xì)內(nèi)容,更多關(guān)于Java如何避免空指針異常的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解IDEA社區(qū)版(Community)和付費(fèi)版(UItimate)的區(qū)別
這篇文章主要介紹了詳解IDEA社區(qū)版(Community)和付費(fèi)版(UItimate)的區(qū)別,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11SpringBoot使用RabbitMQ延時(shí)隊(duì)列(小白必備)
這篇文章主要介紹了SpringBoot使用RabbitMQ延時(shí)隊(duì)列(小白必備),詳細(xì)的介紹延遲隊(duì)列的使用場(chǎng)景及其如何使用,需要的小伙伴可以一起來了解一下2019-12-12Spring Boot配置Thymeleaf(gradle)的簡(jiǎn)單使用
今天小編就為大家分享一篇關(guān)于Spring Boot配置Thymeleaf(gradle)的簡(jiǎn)單使用,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2018-12-12Java中數(shù)據(jù)轉(zhuǎn)換及字符串的“+”操作方法
本文主要介紹了Java中的數(shù)據(jù)類型轉(zhuǎn)換,包括隱式轉(zhuǎn)換和強(qiáng)制轉(zhuǎn)換,隱式轉(zhuǎn)換通常用于將范圍較小的數(shù)據(jù)類型轉(zhuǎn)換為范圍較大的數(shù)據(jù)類型,而強(qiáng)制轉(zhuǎn)換則是將范圍較大的數(shù)據(jù)類型轉(zhuǎn)換為范圍較小的數(shù)據(jù)類型,本文介紹Java中數(shù)據(jù)轉(zhuǎn)換以及字符串的“+”操作,感興趣的朋友一起看看吧2024-10-10SpringBoot @PostConstruct原理用法解析
這篇文章主要介紹了SpringBoot @PostConstruct原理用法解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08Spring Boot修改內(nèi)置Tomcat默認(rèn)端口號(hào)的示例
本篇文章主要介紹了Spring Boot修改內(nèi)置Tomcat端口號(hào)的示例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08