基本数据类型和自动拆/装箱的实现(二)
自动装箱和自动拆箱的实现原理
Java提供了自动拆装箱的能力,那么其中的原理必然需要理解。
那么通过以下几段代码来了解如何实现自动拆装箱的功能:
public static void main(String[] args) {
Integer integer = 1;// 装箱
int i = integer;// 拆箱
}
对以上代码进行反编译:
public static void main(String[] args) {
Integer integer = Integer.valueOf(1);
int i = integer.intValue();
}
从上面的反编译的代码中可以看的出来,int的自动装箱都是通过Integer.valueOf方法实现的,Integer的自动拆箱都是通过integer.intValue来是实现的。如果你尝试将其他的类型进行反编译的话得到的结果都会有一个规律:
自动装箱都是 通过包装类的valueOf方法来实现的,自动拆箱都是通过包装类对象的xxxValue来实现的。
那么那些地方会使用到自动拆装箱呢?
场景一: 将基本数据类型放入集合类
我们知道,Java中的集合类只能接收对象类型,那么下面的代码为什么不回报错呢?
List<Integer> li = new ArrayList<>();
for (int i = 1; i < 50; i ++)
{
li.add(i);
}
反编译
List<Integer> li = new ArrayList<>();
for (int i = 1; i < 50; i += 2)
{
li.add(Integer.valueOf(i));
}
通过反编译的结果,当我们把基本数据类型放入集合类中的时候,会进行自动的装箱。
场景二: 包装类型和基本数据类型进行比较
那我们现在做一个假设,当我们对Integer对象与基本数据类型进行比较的时候,实际上比较的都是些什么内容呢?上代码来看:
Integer a=1;
System.out.println(a==1?"等于":"不等于");
Boolean bool=false;
System.out.println(bool?"真":"假");
反编译:
Integer a=1;
System.out.println(a.intValue()==1?"等于":"不等于");
Boolean bool=false;
System.out.println(bool.booleanValue?"真":"假");
可以看到,包装类与基本数据类型进行比较的时候,先是将包装类分解成基本数据类型,然后进行比较。
场景三 包装类型的计算
那我们再做一个假设,当我们对Integer对象进行四则运算的时候,他们是如何进行运算的呢?
我们上代码来看
Integer i = 10;
Integer j = 20;
System.out.println(i+j);
反编译:
Integer i = Integer.valueOf(10);
Integer j = Integer.valueOf(20);
System.out.println(i.intValue() + j.intValue());
同样的,两个包装类之间的运算还是自动拆箱成为基本类型进行运算。
场景四:三目运算符的使用
我们来看一个简单的三目运算代码:
boolean flag = true;
Integer i = 0;
int j = 1;
int k = flag ? i : j;
反编译:
boolean flag = true;
Integer i = Integer.valueOf(0);
int j = 1;
int k = flag ? i.intValue() : j;
System.out.println(k);
因为例子中,flag ? i : j; 片段中,第二段的 i 是一个包装类型的对象,而第三段的 j 是一个基本类型,所以会对包装类进行自动拆箱。如果这个时候 i 的值为 null,那么就会发生 NPE(自动拆箱导致的空指针异常)
场景五: 函数参数与返回值
这个要是写的话,相信有一定开发经验的同学肯定比较好理解了,直接上代码:
public int getNum1(Integer num) {
return num;
}
//自动装箱
public Integer getNum2(int num) {
return num;
}
自动拆装箱与缓存
Java SE的自动拆装箱还提供了一个功能那就是有关缓存,先上代码:
public static void main(String... strings) {
Integer integer1 = 3;
Integer integer2 = 3;
if (integer1 == integer2)
System.out.println("integer1 == integer2");
else
System.out.println("integer1 != integer2");
Integer integer3 = 300;
Integer integer4 = 300;
if (integer3 == integer4)
System.out.println("integer3 == integer4");
else
System.out.println("integer3 != integer4"); }
不妨在看结果之前,先自己猜测或者分析一下代码运行的结果。
我们普遍认为两个结果都是false。虽然比较的值是相等的,但是由于比较的是对象,而对象的引用不一样,所以会认为两个if条件判断的都是false的,在Java中 == 是比较对象的应用,而equals比较的是值。所以在这个例子中,不同的对象有不同的引用,所以在进行比较的时候都将返回false,奇怪的是这里两个类似的if判断返回的是不同的布尔值。
我们来看一下输出的结果:
integer1 == integer2
integer3 != integer4
原因就和 Integer 中的缓存机制有关。在 Java 5 中,在 Integer 的操作上引入了一个新功能来节省内存和提高性能。整型对象通过使用相同的对象引用实现了缓存和重用。适用于整数值区间 -128 至 +127 。只适用于自动装箱。使用构造函数创建对象不适用。
自动拆装箱带来的问题
当然,自动拆装箱是一个很好的功能,大大节省了开发人员的精力,不再需要关心到底 什么时候需要拆装箱。但是,他也会引入一些问题。 包装对象的数值比较,不能简单的使用==,虽然-128 到 127 之间的数字可以,但是 这个范围之外还是需要使用 equals 比较。 前面提到,有些场景会进行自动拆装箱,同时也说过,由于自动拆箱,如果包装类对象 为 null,那么自动拆箱时就有可能抛出 NPE。 如果一个 for 循环中有大量拆装箱操作,会浪费很多资源。