平时的小细节,总能在关键时刻酿成线上事故,最近在代码中使用了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);
}
自动装箱
三、避坑
上面通过一个小例子,分享了自动拆箱中可能发生的问题,那么应该如何必坑,
1、在自动拆箱的地方进行为null判断;
2、比较的时候尽量做到比较符合两端数据类型一致;
3、平时勤学苦练;
四、总结
自动拆箱时由于调用的是intValue方法,所以如果调用方本身是null的话,肯定会NPE,所以在发生自动拆箱的地方一定要多注意。
自动装箱调用的是静态方法valueOf,自动装箱其实还隐藏了一个更大的秘密,你知道吗,下期见。
编码处处有bug,生活处处有惊喜。对待代码要有敬畏之心,多总结经验。