Java如何優(yōu)雅地避免空指針異常(NullPointerException)
前言
空指針異常是導致java程序運行中斷最常見的原因,相信每個程序猿都碰見過,也就是NullPointException,我們通常簡稱為NPE,為了提高我們寫的代碼的健壯性,本文告訴大家如何優(yōu)雅避免NPE。
出現(xiàn)空指針異常的情況
訪問空對象的屬性或調用空對象的方法
當一個對象是null時,試圖訪問一個對象的屬性或調用其方法,就會觸發(fā)空指針異常。
代碼示例
String text = null; int length = text.length(); User user = null; String userName = user.getUserName();
數(shù)組為null或者數(shù)組元素為null
當嘗試訪問數(shù)組中的某個索引處的元素,而該元素為null
時,同樣會導致空指針異常。
String[] strs = null; int length = strs.length; String[] strs = new String[3]; int length = strs[2].length();
集合中null元素訪問
當集合中存在null元素,當我們遍歷集合,訪問到這個元素的屬性或者方法時也會拋出NPE,這種情況也會出現(xiàn)在我們的日常開發(fā)中,有時候就會因為數(shù)據(jù)問題導致這種情況發(fā)生,常常也莫名其妙。。。。
List<String> list = Lists.newArrayList(); list.add(null); System.out.println(list.get(0).length());
調用的方法返回null
調用某個方法,期望其返回一個非null的對象,但實際返回了null。當然這種情況等同于訪問空對象的屬性或者方法。這在實際開發(fā)過程中極易出現(xiàn)的一種情況。比如我們使用Mybatis
從數(shù)據(jù)庫中查詢一條記錄時,數(shù)據(jù)不存在,就會返回null。這種情況尤為注意。
使用基本數(shù)據(jù)類型的包裝類
在使用基本數(shù)據(jù)類型的包裝類時,如果未正確初始化,再轉成int時,可能導致空指針異常。
Integer i = null; int num = i;
避免NPE的幾種方式
訪問對象前要謹慎
在使用對象之前,始終檢查它是否為null。這包括方法參數(shù)、返回值以及對象的屬性。在訪問對象的方法或屬性之前,使用條件語句判斷對象是否為null。比如我們在訪問User對象前,一定要判null。
如果對象訪問不可避免時,我們也要遵循以下規(guī)則:
在使用對象之前,始終檢查它是否為null
包括方法參數(shù)、返回值以及對象的屬性。在訪問對象的方法或屬性之前,使用條件語句判斷對象是否為null。比如我們在訪問User對象前,一定要判null。
User user = new User(); if (user != null){ String userName = user.getUserName(); Address address = user.getAddress(); if (address != null){ String coutry = address.getCountry(); } }
或者我們的user是從一個方法中獲取的,例如數(shù)據(jù)庫中查詢,那么我們在訪問這個對象前,一定要判null,如果為null要拋出對應的業(yè)務異常,然后我們就可以在接口響應中對應返回錯誤的信息即可,此時就算是一個正常的流程了。這點尤為重要,一定要注意。
User user = userManager.getUserById(Long userId); if (user == null){ throw new ServiceException(""當前查詢的對象不存在); }
當然如果使我們在寫User getUserById(Long id)
返回對象或者List<User> listUserByIds(List<Long> idList)
時我們可以不返回null
,可以返回一個對象默認信息或者一個空集合,這樣調用方就不會出現(xiàn)NPE風險,當然我們不強制返回一個對象或者空集合,但是必須添加注釋充分 說明什么情況下會返回null值。這也是阿里巴巴開發(fā)手冊規(guī)約的建議。
從已知的String對象中調用equals()和equalsIgnoreCase()方法,而非未知對象。
總是從已知的非空String對象中調用equals()方法。因為equals()方法是對稱的,調用a.equals(b)和調用b.equals(a)是完全相同的,這也是為什么程序員對于對象a和b這么不上心。如果調用者是空指針,這種調用可能導致一個空指針異常
Object unknownObject = null ; //錯誤方式 – 可能導致 NullPointerException if (unknownObject.equals( "knownObject" )){ System.err.println( "This may result in NullPointerException if unknownObject is null" ); } //正確方式 - 即便 unknownObject是null也能避免NullPointerException if ( "knownObject" .equals(unknownObject)){ System.err.println( "better coding avoided NullPointerException" ); }
當valueOf()和toString()返回相同的結果時,寧愿使用前者。
因為調用null對象的toString()會拋出空指針異常,如果我們能夠使用valueOf()獲得相同的值,那寧愿使用valueOf(),傳遞一個null給valueOf()將會返回“null”,尤其是在那些包裝類,像Integer、Float、Double和BigDecimal。
BigDecimal bd = getPrice(); System.out.println(String.valueOf(bd)); //不會拋出空指針異常 System.out.println(bd.toString()); //拋出 "Exception in thread "main"
使用Optional類
JDK8以上版本提供了Optional
類,它是一個容器對象,可用于包裝可能為null的值。我們可以使用它判斷null問題,同時也解決了多層級訪問問題,配合使用orElse時,會先執(zhí)行orElse方法,然后執(zhí)行邏輯代碼,不管是否出現(xiàn)了空指針。
//獲取user對象中的Country屬性,如果為空則會存儲一個空字符串到country中 String country = Optional.ofNullable(user) .map(User::getAddress) .map(Address::getCountry) .orElse(""); //獲取user對象中的Country屬性,如果Country屬性為空則會存儲一個空字符串到country中 String country = Optional.ofNullable(user) .map(User::getAddress) .map(Address::getCountry) .orElseGet(() -> defaultContry()); private String defaultContry(){ return "CN"; }
我們還可以使用orElseThrow()方法,當Optional中的對象是一個null時我們直接拋出異常:
String userName = Optional .ofNullable(user) .map(User::getUserName) .orElseThrow(() -> new ServiceException("當前用戶信息不存在"));
定義數(shù)據(jù)庫中的字段是否可為空。
如果你在使用數(shù)據(jù)庫來保存你的域名對象,如Customers,Orders 等,你需要在數(shù)據(jù)庫本身定義是否為空的約束。因為數(shù)據(jù)庫會從很多代碼中獲取數(shù)據(jù),數(shù)據(jù)庫中有是否為空的檢查可以確保你的數(shù)據(jù)健全。在數(shù)據(jù)空中維護null約束同樣可以幫助你減少Java代碼中的空指針檢查。當從數(shù)據(jù)庫中加載一個對象是你會明確,哪些字段是可以為null的,而哪些不能,這可以使你代碼中不必要的!= null檢查最少化。
使用斷言避免空指針
使用Java斷言(assert)來檢查變量是否為null。但要注意,斷言通常在開發(fā)和測試階段啟用,而在生產(chǎn)環(huán)境中可能被禁用(在生產(chǎn)環(huán)境中,通常不會啟用斷言以避免不必要的性能開銷以及防止?jié)撛诘腻e誤信息泄漏)。
User user = new User(); //使用斷言確保 user 對象不為 null。如果 user 為 null,則會拋出 AssertionError, //并顯示消息 "user should not be null"。 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
注解通常用于標記一個方法的參數(shù)、返回值或者字段可能為null。這個注解并非Java標準庫的一部分,但在一些第三方庫(如JSR 305庫中的javax.annotation.Nullable
,以及Google Guava和JetBrains的Kotlin標準庫等)中廣泛使用,并且被許多IDE和靜態(tài)分析工具支持。以便在編譯期或開發(fā)工具中提示可能的NPE風險。
@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); }
此時IDEA就會警告會出現(xiàn)NPE風險
避免從方法中返回空指針,而是返回空collection或者空數(shù)組。
這個Java最佳實踐或技巧由Joshua Bloch在他的書Effective Java中提到。這是另外一個可以更好的使用Java編程的技巧。通過返回一個空collection或者空數(shù)組,你可以確保在調用如size(),length()的時候不會因為空指針異常崩潰。Collections類提供了方便的空List,Set和Map: Collections.EMPTY_LIST,Collections.EMPTY_SET,Collections.EMPTY_MAP。這里是實例。
public List getOrders(Customer customer){ List result = Collections.EMPTY_LIST; return result; }
使用null安全的方法和庫 有很多開源庫已經(jīng)為您做了繁重的空指針檢查工作。
其中最常用的一個的是Apache commons 中的StringUtils。你可以使用StringUtils.isBlank(),isNumeric(),isWhiteSpace()以及其他的工具方法而不用擔心空指針異常。
//StringUtils方法是空指針安全的,他們不會拋出空指針異常 System.out.println(StringUtils.isEmpty( null )); System.out.println(StringUtils.isBlank( null )); System.out.println(StringUtils.isNumeric( null )); System.out.println(StringUtils.isAllUpperCase( null )); Output: true true false false
補充
在JDK 17中引入的Helpful NullPointerExceptions特性確實增強了空指針異常信息的準確性與可用性。當發(fā)生NullPointerException時,JVM現(xiàn)在能夠提供更精確的位置信息,特別是在鏈式調用場景下,它會指出導致空指針異常的具體對象引用。這有助于開發(fā)者更快地定位到代碼中的問題所在,無需通過堆棧跟蹤逐層分析來判斷哪個對象引用為null。假如我們訪問user.getAddress().getCountry().length()
時,在JDK17以前,如果發(fā)生了空指針異常,他只會打印出來發(fā)生了空指針異常,但是并沒有告知到底是user對象還是address對象還是coutnry發(fā)生了異常:
Exception in thread "main" java.lang.NullPointerException at com.study.base.core.base.NpeTest.main(NpeTest.java:23)
但是在JDK17以后,借助Helpful NullPointerExceptions特性,異常信息將更加精確,可能會類似打印這樣的信息,精確到哪個值發(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)
總結
到此這篇關于Java如何優(yōu)雅地避免空指針異常(NullPointerException)的文章就介紹到這了,更多相關Java避免空指針異常內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Java8新特性之空指針異常的克星Optional類的實現(xiàn)
這篇文章主要介紹了Java8新特性之空指針異常的克星Optional類的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-10-10如何查找YUM安裝的JAVA_HOME環(huán)境變量詳解
這篇文章主要給大家介紹了關于如何查找YUM安裝的JAVA_HOME環(huán)境變量的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧。2017-10-10IDEA 中創(chuàng)建Spring Data Jpa 項目的示例代碼
這篇文章主要介紹了IDEA 中創(chuàng)建Spring Data Jpa 項目的示例代碼,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-04-04Spring Boot2與Spring Boot3的區(qū)別小結
SpringBoot2和SpringBoot3之間有一些重要的區(qū)別,本文就來探討SpringBoot2和SpringBoot3之間的區(qū)別,具有一定的參考價值,感興趣的可以了解一下2023-10-10SpringBoot基于SpringSecurity表單登錄和權限驗證的示例
這篇文章主要介紹了SpringBoot基于SpringSecurity表單登錄和權限驗證的示例。文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-09-09如何解決springboot數(shù)據(jù)庫查詢時出現(xiàn)的時區(qū)差異問題
這篇文章主要介紹了如何解決springboot數(shù)據(jù)庫查詢時出現(xiàn)的時區(qū)差異問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-01-01