title:Java自动装箱和拆箱
date:2017年10月27日15:04:35
categories: Java基础
前言
相信大家在写代码的时候,都写过至少看到过这样的代码:
Integer i=100;
那你写的时候,有没有想过为什么可以直接这样写呢,Integer可是一个对象呀,可不是个基本类型,为什么可以直接写Integer i=100呢?应该是
Integer i=new Integer(100);
这样才对把。那么到底为什么在Java里面我们允许这样写呢?
这就涉及到我们今天要探讨的Java自动装箱和拆箱了。
当我们学习完Java自动装箱和拆箱,我们就能很好的把上面的问题解决。
装箱就是自动将基本数据类型转换为包装器类型;拆箱就是自动将包装器类型转换为基本数据类型。
自动拆装箱分析
Example 0
首先,我们还是通过一个例子来分析(JDK1.8)
package com.wangcc.MyJavaSE.box;
import org.junit.Test;
public class IntegerTest {
@Test
public void test() {
int i=125;
Integer i1=new Integer(125);
Integer i2=Integer.valueOf(125);
// int i2_0=i2.intValue();
/*Integer i3=125;
*系统为我们执行了:
Integer i3 = Integer.valueOf(99);
*/
Integer i3=125;
//基本类型和包装类型使用==比较时,包装类型会自动拆箱
/*
* Integer i1=new Integer(125);
* int i1_0=i1.intValue();
* i==i1_0 ?
* */
System.out.println(i==i1); //(1) true
System.out.println(i==i2); //(2) true
System.out.println(i==i3); //(3) true
System.out.println(i1==i2);
System.out.println(i1==i3);
System.out.println(i2==i3);
}
}
我们可以看到执行后的输出如下:
true
true
true
false
false
true
当基本类型和包装类型使用==比较
我们先看(1)的输出结果为什么是true
当i与i1比较的时候,是基本类型与包装类型去比较,这个时候包装类型会自动进行拆箱操作,这部分工作应该是由编译器优化完成的把。(只是个人猜测 javac的功劳)
相当于会执行如下代码:
Integer i1=new Integer(125);
int i1_0=i1.intValue();
i==i1_0 //最后使用Integer对应的基本类型的值来和基本类型的值直接比较
所以我们会得到true
当Integer i3=125;和Integer i2=Integer.valueOf(125);比较
首先我们可以看到结果是true,那为什么是true呢.
先我们就需要看Integer i3=125;
为什么可以这样写呢?实际上这应该也是编译器对这行代码进行了自己的优化。相当于我们编写了如下代码:
Integer i3 = Integer.valueOf(125);
也就是说实际上i2和i3都相当于执行了Integer.valueOf(125)得到的
为什么两次执行这个方法都得到得到的是同一个Integer对象呢?我们得看下相关源码
public static Integer valueOf(inti) {
if(i >= -128 &&i <=IntegerCache.high)
//如果i在-128~high之间,就直接在缓存中取出i的Integer类型对象
return IntegerCache.cache[i + 128];
else
return new Integer(i); //否则就在堆内存中创建
}
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;//h值,可以通过设置jdk的AutoBoxCacheMax参数调整
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
// 取较大的作为上界,但又不能大于Integer的边界MAX_VALUE
i = Math.max(i, 127);//上界最小为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]; //上界确定,此时high默认一般是127
// 创建缓存块,注意缓存数组大小
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);// -128到high值逐一分配到缓存数组
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
我们将上述代码重要部分都加上了注释,我们特别注意在确定i的取值时,我们标注了i的最小值是127,也就是说
Integer的缓存至少要覆盖[-128, 127]的范围,那么为什么符合规范的Java实现必须保证Integer的缓存至少要覆盖[-128, 127]的范围
这个有官方说明:(The Java Language Specification, 3rd Edition)
If the value p being boxed is true, false, a byte, a char in the range \u0000 to \u007f, or an int or short number between -128 and 127, then let r1 and r2 be the results of any two boxing conversions of p. It is always the case that r1 == r2.
为了便于理解,我们给出大概的中文意思
为了节省内存,对于下列包装对象的两个实例,当它们的基本值相同时,他们总是==:
Boolean
Byte
Character, \u0000 - \u007f(7f是十六进制的127)
Integer, -128 — 127
好吧,我还是没理解上面那段英文的意思。
这里给出除了Integer之外的其他类型的自动装箱池
Boolean:(全部缓存)
Byte:(全部缓存 -128 — 127)
Character(0-127缓存)
Short(-128 — 127缓存)
Long(-128 — 127缓存)
Float(没有缓存)
Doulbe(没有缓存)
为什么Float和Double没有缓存呢,其实很简单,因为在一定的范围呢,整型的个数是可以确定的,所以可以指定一个缓存数组,但是在一定的范围内,浮点型的个数是无法确定的,所以,没法指定一个缓存数组。
上面一开始有说过,我们可以通过设置VM虚拟机参数来动态的改变Integer缓存区间大小。通过设置
-XX:AutoBoxCacheMax=NNN参数即可将Integer的自动缓存区间设置为[-128,NNN]。这个参数是server模式专有的。
首先你要确认你的Java服务运行所用到的jdk的默认运行模式是多少,使用javac -version参看,我本机安装的JDK8默认就是Server 模式。
如果你默认的是Client模式的话,你就需要作出一些调整了,找到你所安装jdk的地方,进入到jdk/jre/bin目录,然后搜索jvm.cfg文件,修改文件,将Server Client两行顺序做一个前后调整就好。
扯得有点多了,从上面我们可以得知:
当使用
Integer i=Integer.valueOf(x);
当x在-128~127之间的时候,我们得到的Integer对象是缓存IntegerCache中的对象(默认没有修改相关参数的情况下),所以当多次调用这个方法的时候,返回的都是同一对象,而当x的值不在这个范围的时候,多次调用就会多次创建新的Integer对象了。
Example 1
@Test public void testUnboxing() { Integer num1 = 100; Integer num2 = 200; Long num3 = 300l; System.out.println(num3 == (num1 + num2)); }
当你第一眼看到这个代码的时候,你会给出怎样的答案呢?
true还是false?
答案是true。如果是单纯的Integer对象和Long类型的对象使用==比较,很显然是无法通过编译的。
因为不是同一类型,那么如果是使用equals()这种不比较引用的呢?也是false,还是因为是不同类型。
如果是上述的情况使用==比较。如果在比较的过程中有对某个包装类型进行了+ - * / 等操作时,会自动的进行拆箱操作,最终会比较两端的数值大小。
当 “==”运算符的两个操作数都是 包装器类型的引用,则是比较指向的是否是同一个对象,而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程)。
总结
Java使用自动装箱和拆箱机制,节省了常用数值的内存开销和创建对象的开销,提高了效率。通过上面的研究和测试,结论如下:
(1)Integer和 int之间可以进行各种比较;Integer对象将自动拆箱后与int值比较
(2)两个Integer对象之间也可以用>、<等符号比较大小;两个Integer对象都拆箱后,再比较大小
(3) 两个Integer对象最好不要用==比较。因为:-128~127范围(一般是这个范围)内是取缓存内对象用,所以相等,该范围外是两个不同对象引用比较,所以不等。