<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Java自動拆箱空指針異常,救火隊員上線

          共 7010字,需瀏覽 15分鐘

           ·

          2021-02-28 20:43

          公司搬遷,臨時充當裝修工,提前兩個小時到公司忙著拆卸設(shè)備。結(jié)果接到客戶反映,某部分功能偶爾不能用。于是參與救火,與寫這段代碼的小伙伴一起排查原因。

          最終發(fā)現(xiàn)導致業(yè)務(wù)偶爾不能使用是由Long類型自動拆箱導致空指針異常引起的。下面就帶大家分析一下Java中基礎(chǔ)類型的包裝類在拆箱和裝箱過程中都做了什么,為什么會出現(xiàn)空指針異常,以及面試過程中會出現(xiàn)的相關(guān)面試題。

          問題重現(xiàn)

          下面通過一個簡單的示例才重現(xiàn)一下異常出現(xiàn)的場景。

          public class BoxTest {

              public static void main(String[] args) {
                  Map<String,Object> result = httpRequest();
                  long userId = (Long) result.get("userId");
              }

              // 模擬一個HTTP請求
              private static Map<String,Object> httpRequest(){
                  Map<String,Object> map = new HashMap<>();
                  map.put("userId",null);
                  return map;
              }
          }

          基本的場景就是請求一個接口,去接口中取某個值,這個值為Long類型,從Map中取得值之后,進行Long類型的強轉(zhuǎn)。當接口返回的userId為null時,強轉(zhuǎn)這塊就拋出空指針異常:

          Exception in thread "main" java.lang.NullPointerException
           at com.choupangxia.box.BoxTest.main(BoxTest.java:15)

          上面的場景跟下面的代碼出現(xiàn)異常效果一樣:

          public class BoxTest {

              public static long getValue(long value) {
                  return value;
              }

              public static void main(String[] args) {
                  Long value = null;
                  getValue(value);
              }
          }

          上述代碼也是將Long類型進拆箱導致的異常,只不過一個在代碼中,一個在參數(shù)中。為了分析更簡化,我們以第二個為例進行講解。

          原因分析

          最初大家可能會疑惑,拋出異常的代碼都沒有對象的方法調(diào)用,怎么會出現(xiàn)空指針呢?

          這中間主要涉及到的就是一個自動拆箱操作。是否是拆箱導致的呢?我們來通過字節(jié)碼看一下。

          通過javap -c來查看一下對應(yīng)的字節(jié)碼:

          public class com.choupangxia.box.BoxTest {
            public com.choupangxia.box.BoxTest();
              Code:
                 0: aload_0
                 1: invokespecial #1                  // Method java/lang/Object."<init>":()V
                 4: return

            public static long getValue(long);
              Code:
                 0: lload_0
                 1: lreturn

            public static void main(java.lang.String[]);
              Code:
                 0: aconst_null
                 1: astore_1
                 2: aload_1
                 3: invokevirtual #2                  // Method java/lang/Long.longValue:()J
                 6: invokestatic  #3                  // Method getValue:(J)J
                 9: pop2
                10: return
          }

          其中g(shù)etValue方法調(diào)用對應(yīng)的是main方法中編號3和6的操作。編號3為命令invokevirtual為方法指令。對應(yīng)的便是value.longValue,value對應(yīng)的就是聲明的Long類型。

          也就是說編譯器將getValue(value)拆分成了兩步,第一步將通過value的longValue方法將其拆箱,然后再將拆箱之后的結(jié)果傳遞給方法。相當于:

          long primitive = value.longValue();
          test(promitive);

          對照最開始的代碼,如果value為null的話,那么在調(diào)用longValue方法時便會拋出NullPointerException。

          所以,本質(zhì)上來講,所謂的自動拆箱和裝箱只不過是Java提供的語法糖而已。

          再次證實

          下面用int類型的實例同時證實一下自動拆箱和自動裝箱兩個操作語法糖底層到底是怎么運行的:

          public class IntBoxTest {

              public static void main(String[] args) {
                  Integer index = 11;
                  int primitive = index;
              }
          }

          同樣查看上面代碼的字節(jié)碼:

          public class com.choupangxia.box.IntBoxTest {
            public com.choupangxia.box.IntBoxTest();
              Code:
                 0: aload_0
                 1: invokespecial #1                  // Method java/lang/Object."<init>":()V
                 4: return

            public static void main(java.lang.String[]);
              Code:
                 0: bipush        11
                 2: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
                 5: astore_1
                 6: aload_1
                 7: invokevirtual #3                  // Method java/lang/Integer.intValue:()I
                10: istore_2
                11: return
          }

          可以看到main方法部分,編號2進行了裝箱操作,將原始類型int,裝箱成了Integer,調(diào)用的方法為Integer.valueOf;而編號7進行了拆箱操作將Integer類型轉(zhuǎn)換成了int類型,調(diào)用的方法為Integer.intValue。

          自動拆箱裝箱的本質(zhì)

          通過上面的分析,我們可以看出所謂的拆箱(unboxing)和裝箱(boxing)操作只不過是一個語法糖的功能。編譯器在編譯操作時,本質(zhì)上還是會調(diào)用對應(yīng)包裝類的不同方法來進行處理。

          裝箱時通常會調(diào)用包裝類的valueOf方法,而拆箱時通常會調(diào)用包裝類的xxxValue()方法,其中xxx為類似boolean/long/int等。

          而自動拆箱和裝箱的操作主要發(fā)生在賦值、比較、算數(shù)運算、方法調(diào)用等常見。此時,我們就需要主要空指針的問題。

          面試題

          看一個面試題:請問下面foo1和foo2被調(diào)用時如何執(zhí)行?并簡單分析一下。

          public void foo1() {
              if ((Integer) null == 1) {
              }
          }

          public void foo2() {
              if ((Integer) null > 1) {
                  System.out.println("abc");
              }
          }

          很明顯在調(diào)用兩個方法時都會拋出空指針異常。關(guān)于拋空指針異常的原因及分析過程,上文已經(jīng)講過,大家可以嘗試分析一下字節(jié)碼。

          再看一個面試題:下面的語句能正常執(zhí)行嗎?

          Integer value1 = (Integer) null;
          Double value2 = (Double) null;
          Boolean value3 = (Boolean) null;

          答案:可以正常執(zhí)行。在Java中null是一個特殊的值,可以賦值給任何引用類型,也可以轉(zhuǎn)化為任何引用類型。

          小結(jié)

          任何一個小的問題,小的異常,如果深入追蹤一下,不僅能夠更清楚的明白底層原理,而且還可以在實踐的過程中更有把握,更少犯錯。


          往期推薦

          程序員成為高級管理者的三次躍升

          還不懂Java的泛型?只用這一篇文章,保證你面試對答如流

          放棄FastJson!一篇就夠,Jackson的功能原來如此之牛(萬字干貨)

          經(jīng)過多方調(diào)研,最終還是決定禁用FastJson!

          SpringBoot的四種異步處理,寫這篇文章,我自己先學到了



          如果你覺得這篇文章不錯,那么,下篇通常會更好。添加微信好友,可備注“加群”(微信號:zhuan2quan)

          一篇文章就看透技術(shù)本質(zhì)的人,
            和花一輩子都看不清的人,
            注定是截然不同的搬磚生涯。
          ▲ 按關(guān)注”程序新視界“,洞察技術(shù)內(nèi)幕
          瀏覽 26
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产欧美精品 | 日韩激情视频一区二区三区 | 欧美色综合一区二区三区 | 成人三级视频久久 | 免费中文中文A片 |