个人博客:www.letus179.com
Java是一个近乎纯洁的面向对象编程语言,但是为了编程的方便还是引入了基本数据类型,但是为了能够将这些基本数据类型当成对象操作,Java为每一个基本数据类型都引入了对应的包装类型(wrapper class),int的包装类就是Integer,从Java 5开始引入了自动装箱/拆箱机制,使得二者可以相互转换。
Java 为每个原始类型提供了包装类型:
- 原始类型:
boolean
,char
,byte
,short
,int
,long
,float
,double
- 包装类型:
Boolean
,Character
,Byte
,Short
,Integer
,Long
,Float
,Double
两个常见的面试例子
先看两个常见的例子,后面会针对例子加以分析。
例1
public static void main(String[] args) {
Integer a = new Integer(8);
Integer b = 8;
int c = 8;
System.out.println(a == b);
System.out.println(a == c);
}
执行结果: false, true
例2
public static void main(String[] args) {
Integer f1 = 100, f2 = 100, f3 = 250, f4 = 250;
System.out.println(f1 == f2);
System.out.println(f3 == f4);
}
执行结果: true, false
知识点一:自动拆箱与自动包装
概念
1.自动拆箱: 自动将包装器类型转换为基本数据类型;
2.自动包装: 自动将基本数据类型转换为包装器类型。
具体分析
例1中
Integer b = 8; //自动装箱
//Integer a = new Integer(8);
//int c = 8;
System.out.println(a == c) // 自动拆箱
注意:
Integer与int比较时,会把Integer类型变量拆箱成int类型,然后比较。拆箱调用的是intValue()
方法。
对例1反编译看看(这里用jad
来反编译), Test
是例子中的类名。
下面命令将输出带字节码注释和源码
-a
表示用JVM字节格式来注解输出
;-o
表示无需确认直接覆盖输出
;-s
表示定义输出文件的扩展名
,默认的扩展名是jad
;java
表示我们想要的反编译后输出java
格式文件
jad
详细命令参见反编译小工具:jad常用命令介绍
jad -a -o -s java Test.class
反编译结果:
package test;
import java.io.PrintStream;
public class Test {
public Test() {
// 0 0:aload_0
// 1 1:invokespecial #8 <Method void Object()>
// 2 4:return
}
public static void main(String args[]) {
Integer a = new Integer(8);
// 0 0:new #16 <Class Integer>
// 1 3:dup
// 2 4:bipush 8
// 3 6:invokespecial #18 <Method void Integer(int)>
// 4 9:astore_1
Integer b = Integer.valueOf(8);
// 5 10:bipush 8
// 6 12:invokestatic #21 <Method Integer Integer.valueOf(int)>
// 7 15:astore_2
int c = 8;
// 8 16:bipush 8
// 9 18:istore_3
System.out.println(a == b);
// 10 19:getstatic #25 <Field PrintStream System.out>
// 11 22:aload_1
// 12 23:aload_2
// 13 24:if_acmpne 31
// 14 27:iconst_1
// 15 28:goto 32
// 16 31:iconst_0
// 17 32:invokevirtual #31 <Method void PrintStream.println(boolean)>
System.out.println(a.intValue() == c);
// 18 35:getstatic #25 <Field PrintStream System.out>
// 19 38:aload_1
// 20 39:invokevirtual #37 <Method int Integer.intValue()>
// 21 42:iload_3
// 22 43:icmpne 50
// 23 46:iconst_1
// 24 47:goto 51
// 25 50:iconst_0
// 26 51:invokevirtual #31 <Method void PrintStream.println(boolean)>
// 27 54:return
}
}
可以看到第20
, 22
行,调用了Integer方法.valueOf(int)
自动装箱:
Integer b = 8;
Integer b = Integer.valueOf(8);
第36
行,调用了Integer方法.intValue()
自动拆箱:
System.out.println(a == c);
System.out.println(a.intValue() == c);
所以:a == c
的结果为true
例1中
Integer a = new Integer(8);
Integer b = 8;
System.out.println(a == b);
结果为何为false
?
刚讲到了
Integer b = 8;
调用了Integer方法.valueOf(int)
自动装箱,我们来看下.valueOf(int)
源码实现:
public static Integer valueOf(int i) {
assert IntegerCache.high >= 127;
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
也就是说最后会new
出来一个Integer对象或者返回缓存
中的数据。
注意:
+ ==
符号在比较对象
时,比较的是内存地址
;
+ 对于原始数据类型
(如上面a == c
)直接比对的是数据值
。
这里又涉及到了堆栈内存
了,需要清楚2点:
1. new
出来的对象或创建的数组
会在堆
中开辟内存空间;
2. 对象的引用
(即对象在堆内存
中的地址,如a
)和基本数据类型
存储在栈
中;
由此可知a
,b
引用指向的对象不是同一个,所以结果是false
知识点二:Integer缓存
在上面的.valueOf(int)
源码中我们能看到IntegerCache
类,看名称就知道是和缓存
有关。我们来看下Integer
类的静态内部类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) {
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);
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
}
private IntegerCache() {}
}
该类中有一个静态数组
:
static final Integer cache[];
还有一个静态代码块
:
static {...}
既然是在static
静态类的静态代码快
中,也就是说在类加载的时候就会执行这部分代码逻辑。我们可以看到静态代码快
主要是向静态数组
中添加了[-128,127]
,也就要是说,调用方法.valueOf(int)
传入的int
值在[-128,127]
这个范围内时,直接从IntegerCache
的缓存数组中获取, 不会去在堆内存中new
。
[-128,127]
期间的数字比较常用,这一行为有助于节省内存、提高性能。
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
所以在例2中:
1.自动装箱
调用方法Integer.valueOf(int)
public static void main(String args[])
{
Integer f1 = Integer.valueOf(100);
// 0 0:bipush 100
// 1 2:invokestatic #16 <Method Integer Integer.valueOf(int)>
// 2 5:astore_1
Integer f2 = Integer.valueOf(100);
// 3 6:bipush 100
// 4 8:invokestatic #16 <Method Integer Integer.valueOf(int)>
// 5 11:astore_2
Integer f3 = Integer.valueOf(250);
// 6 12:sipush 250
// 7 15:invokestatic #16 <Method Integer Integer.valueOf(int)>
// 8 18:astore_3
Integer f4 = Integer.valueOf(250);
// 9 19:sipush 250
// 10 22:invokestatic #16 <Method Integer Integer.valueOf(int)>
// 11 25:astore 4
}
2.通过Integer.valueOf(int)
内部调用IntegerCache
类实现。
由于f1
,f2
对应的基本值在[-128,127]
之间,结果返回true
;
而f3
,f4
对应的基本值不在范围内,结果返回false
其他的包装类型也可以类似分析。