什么是 NullPointerException(空指針)

感覺 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荧琼;

  1. 對于傳入的參數(shù)譬胎,方法是底層的方法,傳入?yún)?shù)有可能為 null 命锄,那就需要該方法進行相應(yīng)空指針判斷了堰乔,我就需要拋出對應(yīng)的異常信息;
  1. 如果是調(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());
    }
}

報錯信息


image.png

從圖片分析诅炉,錯誤發(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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市掠拳,隨后出現(xiàn)的幾起案子癞揉,更是在濱河造成了極大的恐慌,老刑警劉巖溺欧,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件喊熟,死亡現(xiàn)場離奇詭異,居然都是意外死亡姐刁,警方通過查閱死者的電腦和手機芥牌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來聂使,“玉大人壁拉,你說我怎么就攤上這事谬俄。” “怎么了弃理?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵溃论,是天一觀的道長。 經(jīng)常有香客問我痘昌,道長钥勋,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任辆苔,我火速辦了婚禮算灸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘姑子。我一直安慰自己乎婿,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布街佑。 她就那樣靜靜地躺著谢翎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪沐旨。 梳的紋絲不亂的頭發(fā)上森逮,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天,我揣著相機與錄音磁携,去河邊找鬼准脂。 笑死,一個胖子當(dāng)著我的面吹牛匾二,可吹牛的內(nèi)容都是我干的豆励。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼统诺,長吁一口氣:“原來是場噩夢啊……” “哼歪脏!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起粮呢,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤婿失,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后啄寡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體豪硅,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年挺物,在試婚紗的時候發(fā)現(xiàn)自己被綠了懒浮。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡识藤,死狀恐怖嵌溢,靈堂內(nèi)的尸體忽然破棺而出眯牧,到底是詐尸還是另有隱情,我是刑警寧澤赖草,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布学少,位于F島的核電站,受9級特大地震影響秧骑,放射性物質(zhì)發(fā)生泄漏版确。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一乎折、第九天 我趴在偏房一處隱蔽的房頂上張望绒疗。 院中可真熱鬧,春花似錦骂澄、人聲如沸吓蘑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽磨镶。三九已至,卻和暖如春健提,著一層夾襖步出監(jiān)牢的瞬間琳猫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工私痹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留脐嫂,地道東北人。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓紊遵,卻偏偏與公主長得像账千,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子暗膜,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容