Integer类是Java开发中最常用的基础类之一,也是基本数据类型int对应的包装类。Integer能使在Java中像类一样使用整型数字,自Java 5引入自动装箱拆箱机制以后,能够混合使用Integer和int来表示整型数字,极大的方便了对数字的操作。同样如果使用不当,也有意想不到的结果产生。所以通过之前的学习和开发过程中踩过的坑,本文总结一下Integer需要注意的一些使用问题和常遇到的相关面试题。
Integer对象 和 int 进行比较
Integer是类,有两种创建方式:= 赋值和new关键字,int是基本类型,只能通过 = 赋值。那么Integer和int比较,结果如何呢?请看下面的代码例子:
public void intCompareInteger() {
for (int i = -200; i < 200; i++) {
int num = i;
Integer integer = i;
Integer integer2 = new Integer(i);
System.out.println(num == integer);
System.out.println(num == integer2);
}
}
// 执行结果:
true
true
true
...
true
运行结果表示数值相同的Integer对象和int变量是相等的,而不会使用对象的比较方法,这也是因为自动拆箱的存在,当基本数据类型与其包装类使用关系操作符 == 进行比较时,其包装类对象会自动拆箱进行运算,因此以上代码是转换为了两个int进行比较。
另外当包装类参与算术操作符运算时,也会被自动转成基本数据类型,请看下面的案例:
public void calIntAndInteger() {
int a = 20;
int b = 38;
Integer c = 18;
Integer c1 = new Integer(18);
System.out.println(b == c + a);
System.out.println(b == c1 + a);
}
// 执行结果:
true
true
因为算术运算符并不适用于Java中的对象,所以参与运算的对象被自动拆箱成了基本数据类型。以上两种现象也是自动拆箱机制所起到的一项重要作用,包装类参与到操作符运算时会先拆箱成基本数据类型,再运算。
自动装箱拆箱解析
上文的案例代码中多处用到了自动装箱拆箱的机制,可以通过字节码了解到具体是如何实现的:
// Java源代码
public static void main(String[] args) {
Integer a = 15;
int b = a;
}
// class字节码
public static main([Ljava/lang/String;)V
L0
LINENUMBER 10 L0
BIPUSH 15
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
ASTORE 1
L1
LINENUMBER 11 L1
ALOAD 1
INVOKEVIRTUAL java/lang/Integer.intValue ()I
ISTORE 2
...
...
}
有上面的字节码可以看出来:
Integer a = 15对应L0,主要是调用了静态方法Integer.valueOf(15),方法内部实现在下文说明;int b = a对应L1,主要调用了成员方法a.intValue(),即直接返回了15。
Integer两种创建方式对比
Integer除了正常创建对象外,还可以通过自动装箱机制,直接接收整型数字。那么这两种方式创建出来的对象又有什么不一样呢?
public void compareInteger() {
// 1
for (int i = -150; i < 150; i++) {
Integer a = i;
Integer b = new Integer(i);
System.out.println("i = " + i + ": " + (a == b));
}
// 2
for (int i = -150; i < 150; i++) {
Integer c = new Integer(i);
Integer d = new Integer(i);
System.out.println("i = " + i + ": " + (c == d));
}
// 3
for (int i = -150; i < 150; i++) {
Integer e = i;
Integer f = i;
System.out.println("i = " + i + ": " + (e == f));
}
}
// 执行结果:
// 1
i = -150: false
i = -149: false
i = -148: false
...
// 2
i = -150: false
i = -149: false
i = -148: false
...
// 3
i = -150: false
...
i = -128: true
...
i = 127: true
i = 128: false
...
根据案例中的执行结果分析,循环1中a和b均不相等,因为有明显的新对象产生,而 == 是比较两个对象的地址值,因此结果容易理解。循环2也是如此。但是循环3中却出现了不同结果,只有在区间 [-128,127] 之间是相等的,那么到底有没有产生新对象呢?为了一探究竟,查看了上文提到的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);
}
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
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);
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
先将该值和IntegerCache类中的两个静态常量high和low对比。那就先看IntegerCache类,它是属于Integer中的一个静态内部类。重点需要关注的有以下两点:
- 包含两个int类型的常量 low = -128 和high 以及一个Integer数组。
- 静态初始化块中,主要将 high 赋值为127,将以 [-128,127] 区间的整数为值的Integer对象存入cache数组。
再来回头看valueOf方法首先判断 i 是否在 low 和 high 这个区间内,是,则直接返回cache数组中值为 i 的Integer对象,否则就会返回新建的一个对象。这也就说明了为什么只在 [-128,127] 之间结果为true的原因,因为此时的对象均为同一个对象。
通过上述问题引出了Integer类中的缓存类,同样的在另外几个包装类中也存在类似的缓存机制,分别缓存了相应的一些值。
| 基本类型 | 包装类 | 取值范围 | 缓存范围 |
|---|---|---|---|
| byte | Byte | -128 ~ 127 | -128 ~ 127 |
| short | Short | -32768 ~ 32767 | -128 ~ 127 |
| int | Integer | -2^31 ~ 2^31-1 | -128 ~ 127 |
| long | Long | -2^63 ~ 2^63-1 | -128 ~ 127 |
| float | Float | 请参见源码 | -- |
| double | Double | 请参见源码 | -- |
| char | Character | \u0000 ~ \uFFFF | \u0000 ~ \u007F |
| boolean | Boolean | true,false | true,false |
原文地址 --- Integer (int)类型分析及相关面试题