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

[java 数值]深入剖析Java中的装箱和拆箱

2017-04-12 21:21 281 查看
自动装箱和拆箱问题是Java中一个老生常谈的问题了,今天我们就来看一下拆箱中的若干问题。本文讲术装箱和拆箱最基本的东本,再来看一下面试笔试中经常遇到的与装箱、拆箱相关的问题。

什么是装箱?什么是拆箱

装箱和拆箱是如何实现的

面试中相关的问题

什么是装箱?什么是拆箱

在前面的文章中提到,Java为每种基本数据类型都提供了对应的包装器类型,至于为什么会为每种基本数据类型提供包装器类型在此不进行阐述,有兴趣的朋友可以查阅相关资料。在Java SE 5 之前,如果要生成一个数值为10的Integer对象,必须这样进行:

Integer i = new Integer(10);


而从Java SE 5 开始就提供了自动装箱的特性,如果要生成一个数值为10的Integer对象,只需要这样就可以了:

Integer i = 10;


这个过程中会自动根据数值创建对应的Integer对象,这就是装箱。

那什么是拆箱呢?顾名思义,跟装箱对应,就是自动将包装器类型转换为基本数据类型:

Integer i = 10;
int n = i;


简单一点说,装箱就是自动将基本数据类型转换为包装器类型;

拆箱就是自动将包装器类型转换为基本数据类型。

int(4字节)Integer
byte(1字节)Byte
short(2字节)Short
long(8字节)Long
float(4字节)Float
double(8字节)Double
char(2字节)Character
boolean (未定)Boolean
二.装箱和拆箱是如何实现的

上一小节了解装箱的基本概念之后,这一小节来了解一下装箱和拆箱是如何实现的。

我们就以Integer类为例,下面看一段代码:

public class Main {
public static void main(String[] args) {
Integer i = 10; int n = i;
}
}


反编译class文件之后得到如下内容:



从反编译得到的字节码内容可以看出,在装箱的时候调用的是Integer.valueOf(int)方法。而在拆箱的时候自动调用的是Integer的intValue方法。其他的也类似,比如Double、Character,不相信的朋友可以自己手动尝试一下。

因此可以用一句话总结装箱和拆箱的实现过程:

**装箱过程是通过调用包装器的valueOf方法实现的,而拆箱过程是通过调用包装器的xxxValue方法实现的。(xxx代表对应的基本数据类型)

三.面试中相关的问题

虽然大多数人对装箱的概念都清楚,但是在面试和笔试中遇到了与装箱和拆箱的问题却不一定会答得上来。下面列举一些常见的与装箱/拆箱有关的面试题。

1.下面这段代码的输出结果是什么?

Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;

System.out.println(i1 == i2);
System.out.println(i3 == i4);


也许有些朋友会说都会输出false,或者也有朋友会说都会输出true。但是事实上输出的结果是:

true
false


为什么会出现这样的结果?输出结果表明i1和i2指向的是同一个对象,而i3和i4指向的是不同的对象。此时只需要看一看源码便知究竟,下面这段代码是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);
}


而其中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() {}
}


从这两段代码可以看出,在通过ValueOf方法创建Integer对象的时候,如果数值在[-128, 127]之间,便返回指向IntegerCache.chache中已经存在的对象的引用;否则创建一个新的Integer对象。

上面的代码中i1和i2的数值为100,因此会直接从cache中取已经存在的对象,所以i1和i2指向的是同一个对象,而i3和i4则是分别指向不同的对象。

2.下面这段代码的输出结果是什么?

Double i1 = 100.0;
Double i2 = 100.0;
Double i3 = 200.0;
Double i4 = 200.0;

System.out.println(i1 == i2);
System.out.println(i3 == i4);


至于具体为什么,读者可以去查看Double类的valueOf的实现。

在这里只解释一下为什么Double类的valueOf方法会采用与Integer类的valueOf方法不同的实现。很简单:在某个范围内的整型数值的个数是有限的,点数却不是。

注意,Integer Short Byte Character Long这几个类的ValueOf方法的实现是类似的。

Double, Float 的valueOf 方法的实现是类似的。

下面这段代码输出的结果是什么:

Boolean b1 = false;
Boolean b2 = false;
Boolean b3 = true;
Boolean b4 = true;


至于为什么是这个结果,同样地,看了Boolean类的源码也会一目也然。下面是Boolean的valueOf方法的具体实现:

public static Boolean valueOf(Boolean b){
return (b ? TRUE : FALSE);
}


而其中的TRUE 和 FALSE又是什么呢?在Boolean中定义了2个表态成员属性:

public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);


至此,大家应该明白了为何上面输出的结果都是true了。

谈谈Integer i = new Integer(xxx) 和Integer i = xxx;的两种方式的区别。

当然,这个题目属于比较宽泛类型的。但是要点一定要答上,我总结一下主要有以下这两点区别:

第一种方式不会触发自动装箱的过程;第二种方式会触发;

在执行效率和资源占用上的区别。第二种方式的执行效率和资源占用在一般性情况下要优于第一种情况(注意这并不是绝对的)。

5.下面程序的输出结果是什么?

Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 321;
Integer f = 321;
Long g = 3L;
Long h = 2L;

//true 因为整数类型会自动缓存-128-127之间的数值类,所以c和d引用的是同一个类。
System.out.println(c==d);
//false 因为 321不在缓存的范围内,所以vm会调用valueOf构造两个新的Integer对象,所以不相等
System.out.println(e==f);
//true a+b 值为3 与 c相比较会触发自动装箱
System.out.println(c==(a+b));
//true
System.out.println(c.equals(a+b));
//true
System.out.println(g==(a+b));
//false a + b 包装成Integer类型,但Long.equals 要求Object为Long引用
System.out.println(g.equals(a+b));
//true 因为h为long类型 a + h会自动转换为long 在遇到equals方法时包装成Long 所以相等
System.out.println(g.equals(a+h));
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: