我们都知道Interge是对Int基础类型的包装类。本着“万物皆对象”,这使一个Int类型能像一个对象一样去使用,并带有自己的方法。比如,在集合类中,我们是无法将int 、double等类型放进去的。因为集合的容器要求元素是Object类型。但是我们又不想因为Interge是封装类,而丢失之前int变量的方便操作:
int i=0; //难道每用一个Integer实例,就要 Integer i=new Integer(1);?
i++; //++ += 操作难道要 每次都调用i.intValue();取出int基础变量进行操作?
i+=1;
所以在JDK1.5 以后引入语法糖—自动拆箱和自动装箱,它的执行是在编译期,会根据代码的语法,在生成class文件的时候,决定是否进行拆箱和装箱动作。
1问— Iteger i=“1”;(自动装箱)
为什么可以写成:
public static void main(String[] args) {
Integer i=1;
}
我们来看看编译后的class文件:
我们可以看到其实编译后,先采用iconst_1指令将常量1压入栈中。实际执行是INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;语句。就是我们执行的语句其实是(这里完成了一个自动装箱操作):
public static void main(String[] args) {
Integer i=Integer.valueOf(1);//这里完成了一个自动装箱操作
}
2问— Integer的类++和+=操作 (自动拆箱、自动装箱)
测试代码:
public static void main(String[] args) {
Integer i=1;
i++;
i+=1;
}
同理我们看看编译后文件:
// access flags 0x9
public static main([Ljava/lang/String;)V
L0
LINENUMBER 4 L0
ICONST_1
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
ASTORE 1
L1 //这里是i++代码块
LINENUMBER 5 L1
ALOAD 1
ASTORE 2
ALOAD 1
INVOKEVIRTUAL java/lang/Integer.intValue ()I //1.这里进行一个拆箱,取到int值
ICONST_1 //2.取到的int压入栈中
IADD //3.完成+1操作
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;//4.再次装箱,将int转成integer
DUP
ASTORE 1
ASTORE 3
ALOAD 2
POP
L2 //这里是+=操作,同理
LINENUMBER 6 L2
ALOAD 1
INVOKEVIRTUAL java/lang/Integer.intValue ()I
ICONST_1
IADD
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
ASTORE 1
.
.
.
}
上面的注解就解释了,而Integer.intValue ()就是返回Integer实例中的int值。所以这就是在++、+=操作下拆箱、装箱的具体实现。
3问—Integer==int(自动拆箱)
演示代码:
public static void main(String[] args) {
Integer integer=128;
int i_1=-128;
int i_2=128;
System.out.println(integer == i_1);
System.out.println(integer == i_2);
}
结果很明显是:
false
true
因为我们知道"==“对基础数据类型就是判断是否相等,对引用对象就是判断引用地址是否一致。但这里是"引用”==基础变量,那最后是怎么完成的呢:
//这里是"integer == i_1"编译代码块
LINENUMBER 7 L3
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ALOAD 1
INVOKEVIRTUAL java/lang/Integer.intValue ()I //我们可以看到还是进行了拆箱,获取int值。
ILOAD 2
IF_ICMPNE L4 //完成比较
ICONST_1
GOTO L5
所以这里是进行了自动拆箱。
4问—Integer==Integer 问题
上面一问,我们就知道了"=="对“Integer”==“Integer”这类引用的比较会进行地址比较,那木下面代码呢:
public static void main(String[] args) {
Integer a_1=127;
Integer a_2=127;
Integer b_1=-128;
Integer b_2=-128;
Integer c_1=-129;
Integer c_2=-129;
System.out.println(a_1==a_2);
System.out.println(b_1==b_2);
System.out.println(c_1==c_2);
}
答案却是:
true
true
false
为什么呢,在这 Integer a_1=127; 我们知道是进行装箱的,会调用valueOf(int i)方法的,那我们去看看这个方法:
明显的是i进去以后,会先进行一个判断,若在[-128,127]区间,就会直接返回IntegerCache.cache数组相应下表的位置。(对于IntegerCache将在下面给出解释) 所以这就是为什么a系列和b系列会判ture,而c系列是false,因为c系列两值进去等会new 一个新对象,那么他们两分配的地址也就不可能相等了。
拓展:
为提高效率和性能,引入了一个IntegerCache来节省内存和提高性能。整型对象通过使用相同的对象引用实现了缓存和重用。
其他包装类一样,但为什么是[-128,127]区间?
因为1字节=8位(bit)。java中的整型属于有符号数。
8bit可以表示的数字:
最小值:10000000 (-128)(-2^7)
最大值:01111111(127)(2^7-1)