之前一篇文章聊過異常排名《Java異常排行榜:哪個異常最常見?》,裡面談到國外一個網站對 Java 異常進行數據分析並排名,結果是 NullPointerException 排第一,本文正好對空指針異常做一個總結,希望對各位同學有所幫助。
在本文中,我展示了一個關於如何處理空指針異常的綜合示例。在Java中,null 作為一個特殊值被對象引用,用來表示該對象當前指向的是一塊未知內存數據。然而NullPointerException這個異常,則是程序在使用或訪問一個對象的引用時,而該對象等於null則被拋出。
那些情況會引發該異常呢?
- 被調用方法的對象為null。
- 訪問或修改一個null對象的字段。
- 求一個數組為null對象的長度。
- 訪問或修改一個數組為null對象中的某一個值。
- 被拋出的值是null並且是一個Throwable的子類。
- 當你用null對象進行synchronized代碼塊。
NullPointerException 是 RuntimeException 的子類,因此,Javac 編譯器並不會強迫你使用 try-catch 代碼塊來捕獲該異常。
一、為什麼需要 null ?
如上所述,null 是 Java 的一個特殊值。它在設計模式方面編碼的過程中非常有用,例如空對象模式和單例模式。空對象模式提供了一個對象作為缺少給定類型對象的代理。而單例模式可以確保只創建一個類的實例,主要用於提供一個全局訪問的對象。
二、如何避免空指針異常
另外,如果引發異常,請使用堆棧中的異常信息進行跟蹤。堆棧跟蹤是由JVM提供,便於應用程序的調試。找到發生異常的方法和代碼行,然後確定哪個引用為null。
在本文的餘下部分,我們將介紹一些避免空指針異常的方法。但是,這些方法並非一勞永逸,因此,同學們在編寫應用程序時應格外小心。
1、String變量與文本值比較
在編碼過程中,String變量與文本值之間的比較是特別常見的。一般被比較的值可以是一個字符串或枚舉值。因此,我們不要從空對象調用方法進行比較,而應考慮從文字值中調用方法。如下:
上面的代碼片段則會拋出一個NullPointerException。但是,如果我們從文字中調用方法,那麼執行流程通常會繼續:
為什麼呢?去看一下 JDK 源碼 java.lang.String 便明白了。
2、檢查方法的參數
在執行你自己的方法的主體之前,一定要檢查方法傳入的參數是否為空。只有在正確檢查了參數後,才能繼續執行該方法的相應邏輯。否則,您可以拋出一個 IllegalArgumentException 來通知調用方法所傳遞的參數有問題。
例如:
3、優先使用String.valueOf() 代替toString()
當您代碼中的某個對象需要用字符串的方式來表示時,請避免使用該對象的toString方法;因為若你的對象引用為null,則會拋出 NullPointerException。
相反,考慮使用靜態String.valueOf方法,該方法不會拋出任何異常,若對象引用為空,則打印「null」字符串。
有的同學可能會問為什麼呢?還是那句話讀源碼 ^_^
下面是基類 Object 的 toString() 方法:
接著,咱們再來看看 String 的 valueOf() 方法做了什麼呢?
4、使用三元運算符
該操作是非常有用的,可以幫助我們避免了NullPointerException。格式如下:
上面通過布爾表達式來判斷。如果表達式結果為true,則返回value1,否則返回value2。我們可以使用三元運算符來處理空指針,如下所示:
如果str的引用為空,則消息變量將為空。否則,如果str指向實際數據,則該消息將保留它的前10個字符。
這裡,一直有個疑惑困擾著我,為什麼 Kotlin 這門語言去掉了三元運算符呢?知道的同學歡迎留言,一起來探討~~~
5、創建返回空集合而不是null值的方法
一個非常好的操作是創建返回一個空集合的方法,而不是一個null值。因為你的代碼可以遍歷空集合並使用它的方法和字段,而不會拋出一個NullPointerException 。例如:
注意:要熟悉 Collections 這個集合工具類,裡面有太多好用的方法了。
6、使用Apache的StringUtils類
Apache的Commons Lang是一個為 java.lang API 提供幫助工具的庫,比如字符串操作方法。提供字符串操作的示例類是 StringUtils.java,它對輸入的字符串進行了 null 判斷。
你可以使用 StringUtils.isNotEmpty, StringUtils.IsEmpty 和 StringUtils.equals 等方法,來避免NullPointerException。例如:
還是老規矩,咱們來讀一下相應的源碼。
7、習慣用 contains(), containsKey(), containsValue() 方法
如果您的程序在使用集合,請考慮使用contains,containsKey和containsValue方法。例如,從集合中找一個特定鍵的值:
在上面的代碼片段中,我們未檢查key是否真的存在於內部Map,因此返回的值可以是 null 。最安全的方法如下:
8、請檢查使用的外部方法的返回值是否為 null
在編碼中使用外部庫是很常見的,這些庫可能包含返回引用的方法,需確保返回的值不為 null 。另外,我們要養成在開發的過程中,養成閱讀 Javadoc 的習慣,以便更好地理解其功能和返回值。
9、使用斷言
斷言在測試代碼時非常有用,並且可以被使用,以避免 NullPointerException 。Java 斷言是用 assert 關鍵字實現的,並拋出一個 AssertionError 。
請注意,您必須顯式啟用 JVM 的斷言標誌,一般在程序啟動時,使用 –ea 參數來啟用斷言。否則,斷言將被完全忽略。
使用 Java 斷言的示例如下:
如果您執行上面的代碼段並傳遞一個空參數getLength,則會出現以下錯誤消息:
最後,您可以使用測試框架 JUnit 提供的類 Assert 來使用斷言。
10、單元測試
在測試代碼的功能和正確性時,單元測試一般非常有用。因此,建議多花一些時間編寫一些測試用例,來避免程序出現 NullPointerException。目前,我司的代碼覆蓋率要達到 95% 以上才能通過。
三、擁有 NullPointerException 的安全方法
1、訪問類的靜態成員或方法
當你的代碼試圖訪問靜態變量或類的方法時,即使對象的引用等於 null,JVM 也不會拋出一個 NullPointerException 。這是由於Java編譯器在編譯過程中將靜態方法和字段存儲在方法區或者常量池。因此,靜態字段和方法不與對象相關聯,而與類的名稱相關聯。
例如,下面的代碼不會拋出NullPointerException:
注意,儘管 SampleClass 等於的實例 null 將會被正確執行。但是,對於靜態方法或字段,最好以靜態方式訪問它們,比如SampleClass.printMessage()。
2、instanceof 操作符
instanceof 即使對象的引用等於 null,也可以使用該運算符。在 instanceof 操作時,參考值等於為null,不拋出 NullPointerException,而是返回 false 。例如,下面的代碼片段:
正如預期的那樣,執行的結果是:
四、參考
https://github.com/apache/commons-lang
https://github.com/junit-team/junit4
https://examples.javacodegeeks.com/java-basics/exceptions/java-lang-nullpointerexception-how-to-handle-null-pointer-exception
閱讀更多 憶蓉之心 的文章