平時的小細節,總能在關鍵時刻釀成線上事故,最近在代碼中使用了Integer的自動拆箱功能,結果NPE(NullPointException)了,悲劇啊。。。
一、何爲自動拆箱
要說自動拆箱,就必須說自動裝箱,當然這裏拆箱和裝箱不是平時的把一個東西放到紙箱子裏進行包裝的意思,這裏的裝箱也有包裝的意思,但包裝的東西卻不是可以看的見的物件。
學過java的都知道,java中的數據類型分爲基本類型和引用類型,基本數據類型中有byte,short,int,long,char,folat,double,boolean,每種基本類型又有其包裝類Byte,Short,Integer,Character,Float,Double,Boolean,這些包裝類也可以稱之爲引用類型,這裏的裝箱和拆箱說的就是八種基本數據類型和其包裝類之間的故事,自動裝箱和自動拆箱有好處也有不好的地方,用不好就會造成很大的傷害。
二、事故復現
1、事故重現
這裏計劃用簡單的代碼,復現下自動拆箱的NPE,
這裏有一個Person類,裏邊有以下的屬性,注意這裏我把其age屬性的數據類型設置爲了Integer
package com.my.unbox; /** * @author wangcj5 * @date 2022/4/16 11:09 */ public class Person { /** * 年齡 */ private Integer age; /** * 姓名 */ private String name; /** * 家庭住址 */ private String address; public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } }
下面直接上測試類
package com.my.unbox; import java.time.Period; import java.util.Objects; /** * @author wangcj5 * @date 2022/4/16 11:11 */ public class TestPerson { private static final int YONG_MAN=18; private static final int OLD_MAN=60; public static void main(String[] args) { //正常情況下 Person person=new Person(); person.setAge(16); System.out.println(isYoung(person)); //非正常情況下 Person person1=new Person(); System.out.println(isYoung(person1)); } /** * 通過年齡判斷一個人是否爲少年,小於18 * @param person * @return */ private static boolean isYoung(Person person){ if(Objects.nonNull(person)){ if(YONG_MAN<person.getAge()){ return true; } } return false; } }
小夥伴們看,測試類也很簡單,裏邊有個方法,判斷一個Person對象是否爲年輕人,通過其age屬性進行判斷,那麼測試結果如下,
false Exception in thread "main" java.lang.NullPointerException at com.my.unbox.TestPerson.isYoung(TestPerson.java:32) at com.my.unbox.TestPerson.main(TestPerson.java:22) Process finished with exit code 1
在第32行發生了NPE,第32行處的代碼如下,
if(YONG_MAN<person.getAge()){
下面來分析下這行代碼,首先person肯定不爲null,因爲上面已經進行了非空判斷,那麼就說person.getAge()爲null,從調用的地方第22行
System.out.println(isYoung(person1));
也就是說person1不爲null,那麼就是person1中的age屬性爲null,由於這裏僅僅new了一個person對象未對age賦值,那麼對於Integer屬性的age默認爲null,這裏也就不奇怪了,問題回到了比較的地方,一個int類型的值和null進行數學比較,這裏就會發生拆箱,即把爲null的age進行拆箱,在這裏發生了NPE。現在就明白了在進行拆箱的時候如果被拆得對象爲null肯定會NPE,那麼java是如何拆箱的,繼續往下看
2、拆箱的本質
要了解拆箱的本質肯定不能草草了事,通過反編譯後的代碼看下,把TestPerson進行反編譯,使用javap命令,
PS C:\05code\Design\target\classes\com\my\unbox> javap -c -p TestPerson.class
得到下面的結果,重點看第32行拆箱的部分,
看上圖紅框內的,看後面的註釋,第一句是調用getAge()方法得到其值,第二句是調用了Integer.intValue()方法,也就是說拆箱調用的Integer.intValue()方法,現在看下該方法的源碼,
public int intValue() { return value; }
看到嗎,就是直接返回value。回到問題的本質爲什麼會發生NPE,也就是在拆箱時調用intValue()方法由於到到age爲null,即,null.intValue(),這不就發生了NPE。
自動拆箱 實際調用的是intValue()方法
下面看自動裝箱,看一個int類型的變量如何稱爲Integer
public class TestBoxing { public static void main(String[] args) { Integer integer=10; } }
反編譯後,
可以看到調用了Integer的valueOf()方法,且該方法是靜態的,如下
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
自動裝箱 實際調用的靜態方法valueOf(int i)方法
三、避坑
上面通過一個小例子,分享了自動拆箱中可能發生的問題,那麼應該如何必坑,
1、在自動拆箱的地方進行爲null判斷;
2、比較的時候儘量做到比較符合兩端數據類型一致;
3、平時勤學苦練;
四、總結
自動拆箱時由於調用的是intValue方法,所以如果調用方本身是null的話,肯定會NPE,所以在發生自動拆箱的地方一定要多注意。
自動裝箱調用的是靜態方法valueOf,自動裝箱其實還隱藏了一個更大的祕密,你知道嗎,下期見。
編碼處處有bug,生活處處有驚喜。對待代碼要有敬畏之心,多總結經驗。