您的位置:首页 > 编程语言 > Java开发

java自动装箱和拆箱机制详解

2016-04-12 23:46 881 查看
我们还是从一个笔试题谈起把?

/**
* Created by dave on 2016/4/12.
* 注释表示运行结果
*/
public class Main {
public static void main(String[] args) {
Integer int1 = new Integer(30);
Integer int2 = 30;
Integer int3 = Integer.valueOf(30);
int int4 = 30;

Integer int5 = 300;
Integer int6 = Integer.valueOf(300);

System.out.println(int1 == int2);//false
System.out.println(int1 == int3);//false
System.out.println(int1 == int4);//true
System.out.println(int2 == int3);//true
System.out.println(int2 == int4);//true
System.out.println(int5 == int6);//false
}
}


这个答案我相信出乎很多人意料。接下来还是以自问自答的方式来解释。

基础类型与对象类型的区别

在Java中一切皆为对象,可唯独基础类型很奇怪,对于从C语言过渡过来的同学可能很容易就接受了,但是却并不一定能搞懂这两者之间的关系。

对于对象类型,是在程序运行时,通过new动态在堆上创建的对象,而基础类型,是在编译的时候就将值分配到数据栈中。通过基础类型,变量地址所存储的值恰好为存储的值;而对象类型,你持有的仅仅是引用而并不是真实数据存储的地址。

基础类型,是将数据分配到了数据栈中,而对象类型,则将对象分配到了对象堆中。据说这也是java保留基础类型的原因,因为性能!保存在数据栈中避免了运行时动态分配内存的操作。

另外一点,还需要注意的是,java中有8种基础类型:

byte\short\int\long\double\float\boolean\char

这里面并没有String!!!

自动装箱机制

Autoboxing is the automatic conversion that the Java compiler makes between the primitive types and their corresponding object wrapper classes. For example, converting an int to an Integer, a double to a Double, and so on.

上述一段话为java官网对自动装箱的表述。也就是说java编译器在编译代码时,将根据需要将基础类型封装成对象类型。

那么问题来了,是如何封装的?是new Integer(int i)????

我们看了官方的代码:

the compiler converts the previous code to the following at runtime:

List li = new ArrayList<>(); for (int i = 1; i < 50; i += 2)

li.add(Integer.valueOf(i));

这表示,编译器在编译之前将list.add(i)替换成了list.add(Integer.valueOf(i));这是所谓的预处理??不懂了。不过不重要。

明确,这里是通过valueOf将基础类型转换成对象类型是很重要的!

自动拆箱机制

由上面我们推断出java在自动拆箱时采用了相似的过程,在预处理阶段,发现在需要基础类型时,将包装类型转换成基础类型,那么这个功能是如何实现的呢?

The compiler does not generate an error because it invokes the intValue method to convert an Integer to an int at runtime:

public static int sumEven(List li) {

int sum = 0;

for (Integer i : li)

if (i.intValue() % 2 == 0)

sum += i.intValue();

return sum; }

由上述代码可以看出,在拆包时,通过调用对象的intValue()来实现拆包。

OK,接下来,我们一步一步聊问题中的代码。

step1

第一步和普通的对象创建没有任何区别,也即在堆中创建一个对象类型。

Integer int1 = new Integer(30);


2、step2

下面两个表达式是一样的,直接给Integer赋值等价于Integer.valueOf()表达式,创建的对象也保存在堆中

Integer int2 = 30;
Integer int3 = Integer.valueOf(30);


3、

两个对象之间进行==比较,实际上是比较的对象引用所存的地址,所以为false

System.out.println(int1 == int2);//false
System.out.println(int1 == int3);//false


4、

对象类型和基础类型进行比较时,对象类型将自动拆箱,而自动拆箱也就是调用对象的IntValue(),即获取对象的值。所以肯定为true

System.out.println(int1 == int4);//true


5、

这个结果可能是最不容易理解的,为什么30时为true,而300为false呢?

Integer int2 = 30;
Integer int3 = Integer.valueOf(30);
System.out.println(int2 == int3);//true

Integer int5 = 300;
Integer int6 = Integer.valueOf(300);
System.out.println(int5 == int6);//false


我们查看了valueOf的源码,其中表述为:

public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}


其中IntegerCache为内部类

private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];

static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;

cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);

// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}

private IntegerCache() {}
}


因此,在加载IntegerCache类时,会自动创建一个cache数组,该数组已经预先创建了一些Integer对象,这个cache数组是从-128到127之间的。当然,最大值也是可以调的,在安装虚拟机的时候可以设置。不过一般就是从-128到127。

因此,当调用valueOf时,如果int值在-128和127之间,直接返回已经创建好的对象,因此,如果有多个表达式调用valueOf,且值在cache区间中,那么这些表达式获取的将是一个Integer对象,于是在进行==比较时自然就相等了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java 自动装箱 Integer