感覺 NullPointerException (空指針異常)一直對我陰魂不散,大部分 bug 都是這個引起的与境,有時候我在想到底什么是 NullPointerException 呢,哪又該如何預(yù)防 NullPointerException ?
之前在看技術(shù)號文章( Stack Overflow),有人提問:什么是 NullPointerException棺榔,沒想到這個問題還挺火的。
一隘道、拋磚引玉 —— 指針概念
C / C++ 中的指針的症歇,是程序數(shù)據(jù)在內(nèi)存中的地址郎笆,而指針變量是用來保存這些地址的變量。
可以把指針理解為地址忘晤。
而在 Java 中是沒有指針這個概念宛蚓,人們稱它問為引用(對象引用)。它需要指向一個實例對象(通過new方法構(gòu)造)的设塔。
二凄吏、來龍去脈 —— what is NullPointerException
NullPointerException (空指針異常),因為引用沒有指向具體的實例闰蛔,所以當(dāng)訪問這個引用的方法的時候就會產(chǎn)生這種異常痕钢。
好比一個風(fēng)箏(對象),手里的線(指針)牽引著它序六,突然線斷了任连,風(fēng)箏飛走了,這就是所謂的空指針異常例诀。
翻譯過來:你找不到對象课梳,因為你對象丟了。
String str = "測試字符串";
System.out.println(str.length());
//上面的代碼沒有問題余佃,但是如果改成下面的代碼:
String str ;//初始化為 null
System.out.println(str.length());//就會產(chǎn)生NullPointerException異常了
三、極往知來 —— 哪些地方可能產(chǎn)生 NullPointerException
那么問題來了跨算,哪些地方有出現(xiàn) NPE 的可能爆土,打仗就得知道“敵人”是誰,它在哪诸蚕。
當(dāng)返回類型為基本數(shù)據(jù)類型步势, return 包裝數(shù)據(jù)類型的對象時,自動拆箱有可能產(chǎn)生 NPE背犯。
數(shù)據(jù)庫的查詢結(jié)果可能為 null坏瘩。
集合里的元素即使 isNotEmpty ,取出的元素也可能為 null 漠魏。
遠程調(diào)用返回對象時倔矾,一律要求進行空指針判斷,以防止 NPE柱锹。
對于 Session 中獲取的數(shù)據(jù)哪自,建議進行 NPE 檢查,以避免空指針禁熏。
級聯(lián)調(diào)用 obj.getA().getB().getC(); 的一連串調(diào)用壤巷,容易產(chǎn)生 NPE 。
PS:可以使用 JDK8 的Optional 類來防止出現(xiàn)NPE 問題瞧毙。
四胧华、未雨綢繆 —— 預(yù)防 NullPointerException
防止 NPE 問題是一個程序員的基本素養(yǎng)寄症。
NPE 問題多了有點煩,于是瘋狂的用 if 條件判斷矩动,雖然是暴力解決但代碼亂七八糟的有巧,一點都不美觀。
public void doWork(String id){
if(StringUtils.isNotEmpty(id)){
//to do
}
Object object = find.Object(id);
if(object != null){
//to do
}
}
看了一些資料后才知道有些地方需要處理空指針铅忿,有些地方則不需要剪决。
1)對于傳入的參數(shù),方法非底層方法檀训,那么只按照自己認定的方式處理柑潦。比如你調(diào)用我的方法 A 并且傳入?yún)?shù),我要求這個參數(shù)不能為null峻凫,如果為null 就會拋出 NullpointerException渗鬼,然后只需要按照不為 null 的方式處理,不會判斷是否為 null荧琼;
- 對于傳入的參數(shù)譬胎,方法是底層的方法,傳入?yún)?shù)有可能為 null 命锄,那就需要該方法進行相應(yīng)空指針判斷了堰乔,我就需要拋出對應(yīng)的異常信息;
- 如果是調(diào)用其他方法得到的結(jié)果脐恩,如果一定需要不為 null 的镐侯,那么就需要判空,因為你同樣需要拋出對應(yīng)的異常信息驶冒;
五苟翻、兵來將擋 —— 尋找并處理 NullPointerException
意外總會發(fā)生,當(dāng) NullPointerException 發(fā)生了骗污,別慌崇猫,可以根據(jù)堆棧信息,找到錯誤需忿。
比如以這個 demo 為例
public class Main {
public static void main(String[] args) {
doWork(null);//故意傳 null
}
public static void doWork(String str){
System.out.println(str.length());
}
}
報錯信息
從圖片分析诅炉,錯誤發(fā)生在 “at …” 列表處,第一個“at 處”就是錯誤最初發(fā)生的位置屋厘。
帶有一系列異常的示例
有時汞扎,應(yīng)用程序會捕獲異常并將其重新引發(fā)為另一個異常的原因。通成谜猓看起來像:
34 public void getBookIds(int id) {
35 try {
36 book.getId(id); // this method it throws a NullPointerException on line 22
37 } catch (NullPointerException e) {
38 throw new IllegalStateException("A book has a null property", e)
39 }
40 }
這可能會給您一個堆棧跟蹤澈魄,如下所示:
Exception in thread "main" java.lang.IllegalStateException: A book has a null property
at com.example.myproject.Author.getBookIds(Author.java:38)
at com.example.myproject.Bootstrap.main(Bootstrap.java:14)
Caused by: java.lang.NullPointerException
at com.example.myproject.Book.getId(Book.java:22)
at com.example.myproject.Author.getBookIds(Author.java:36)
... 1 more
與此不同的是“Caused by”。有時仲翎,例外會有多個“Caused by” 部分痹扇。對于這些铛漓,您通常希望找到“根本原因”,這將是堆棧跟蹤中最低的 “ Cause by” 部分之一鲫构。在我們的例子中是:
Caused by: java.lang.NullPointerException <-- root cause
at com.example.myproject.Book.getId(Book.java:22) <-- important line
同樣浓恶,對于此例外,我們會想看看行22的Book.java结笨,看看有什么可能導(dǎo)致NullPointerException這里包晰。
庫代碼更令人生畏的示例
通常,堆棧跟蹤要比上面的兩個示例復(fù)雜得多炕吸。這是一個示例(雖然很長伐憾,但是展示了多個級別的鏈接異常):
javax.servlet.ServletException: Something bad happened
.....
Caused by: com.example.myproject.MyProjectServletException
at com.example.myproject.MyServlet.doPost(MyServlet.java:169)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)
at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:511)
....
Caused by: org.hibernate.exception.ConstraintViolationException: could not insert: [com.example.myproject.MyEntity]
at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:96)
at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)
at org.hibernate.id.insert.AbstractSelectingDelegate.performInsert(AbstractSelectingDelegate.java:64)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2329)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2822)
at org.hibernate.action.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:71)
at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:268)
.....
Caused by: java.sql.SQLException: Violation of unique constraint MY_ENTITY_UK_1: duplicate value(s) for column(s) MY_COLUMN in statement [...]
at org.hsqldb.jdbc.Util.throwError(Unknown Source)
at org.hsqldb.jdbc.jdbcPreparedStatement.executeUpdate(Unknown Source)
... 54 more
在這個例子中,還有更多赫模。我們最關(guān)心的是從代碼中尋找方法树肃,這些方法可能是com.example.myproject 程序包中的任何方法。在上面的第二個示例中瀑罗,我們首先要查找根本原因胸嘴,即:
Caused by: java.sql.SQLException
但是,該方法下的所有方法調(diào)用都是庫代碼斩祭。因此劣像,我們將移至其上方的“ Caused by”,并尋找源于我們代碼的第一個方法調(diào)用摧玫,即:
at com.example.myproject.MyEntityService.save(MyEntityService.java:59)
就像在前面的例子中驾讲,我們應(yīng)該看看MyEntityService.java就行59,因為這是此錯誤的起源(這個有點明顯出了什么問題席赂,因為SQLException中發(fā)生的錯誤,但調(diào)試程序是什么时迫,我們以后是)颅停。
參考文獻:《阿里巴巴 Java 開發(fā)手冊》
https://www.cnblogs.com/liaochong/p/code.html
https://blog.csdn.net/JavaEETeacher/article/details/4285488
https://stackoverflow.com/questions/218384/what-is-a-nullpointerexception-and-how-do-i-fix-it
https://stackoverflow.com/questions/3988788/what-is-a-stack-trace-and-how-can-i-use-it-to-debug-my-application-errors