Integer经典面试问题:两个Integer对象都赋值为128,这两个对象比较是否相同?为什么?

回答这个问题,首先我们要知道,在Java中,当你写Integer a = 1; 实际上是调用了Java的自动装箱功能。这会将整数 1 自动装箱为Integer对象,然后将这个对象赋值给变量a。

自动装箱功能是由编译器自动插入的,实际上它相当于执行了如下的代码:

1
Integer a = Integer.valueOf(1);

这里的valueOf方法是Integer类的一个静态方法,它的作用就是将传入的参数(通常是基本数据类型)自动转换为对应的包装类对象。对于Integer类来说,就是将整数值转换为Integer对象。

知道这一点之后,我们看看Integer.valueOf方法的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* Returns an {@code Integer} instance representing the specified
* {@code int} value. If a new {@code Integer} instance is not
* required, this method should generally be used in preference to
* the constructor {@link #Integer(int)}, as this method is likely
* to yield significantly better space and time performance by
* caching frequently requested values.
*
* This method will always cache values in the range -128 to 127,
* inclusive, and may cache other values outside of this range.
*
* @param i an {@code int} value.
* @return an {@code Integer} instance representing {@code i}.
* @since 1.5
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}

注解上说:如果不需要新的Integer实例,则通常应该优先使用此方法,而不是构造函数Integer(int),因为通过缓存频繁请求的值,该方法可能会产生更好的空间和时间性能。此方法将始终缓存-128到127(包括-128到127)范围内的值,并可能缓存此范围之外的其他值。

接下来看看IntegerCache缓存的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* Cache to support the object identity semantics of autoboxing for values between
* -128 and 127 (inclusive) as required by JLS.
*
* The cache is initialized on first usage. The size of the cache
* may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
* During VM initialization, java.lang.Integer.IntegerCache.high property
* may be set and saved in the private system properties in the
* sun.misc.VM class.
*/

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

缓存在第一次使用时初始化。缓存的大小可以通过-XX:AutoBoxCacheMax=选项来控制。在虚拟机初始化过程中,可以设置java.lang.Integer.IntegerCache.high属性,并保存在sun.misc.VM类的私有系统属性中。

IntegerCache缓存范围的最小值固定为-128,最大值默认为127.但是最大值是可以被重新定义的,那么可定义的范围是多少呢?

1
2
3
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);

上面的两行已经定义了IntegerCache缓存最大值的范围:

  1. 定义最大值范围大于等于127;

  2. 定义最大值范围小于等于Integer.MAX_VALUE - (-low) -1;

Integer.MAX_VALUE的定义:(2的23次方 - 1)

1
@Native public static final int   MAX_VALUE = 0x7fffffff;

IntegerCache缓存的范围定义好后,为区间范围赋值。

1
2
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);

最后通过断言检查 IntegerCache.high 是否大于等于 127。

1
2
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;

IntegerCache.high 是 Java 中 Integer 缓存的一部分,这个缓存用于存储 -128 到 127 之间的整数,以提高这个范围内的整数在多次使用时的性能。

assert 是一个用于调试的语句,当 IntegerCache.high < 127 时,该语句将抛出 AssertionError

JLS7 5.1.7 是 Java Language Specification 的一个部分,这部分规定了对于字符串字面值,编译器必须将它们在编译期放入字符串池中,以避免内存浪费。

这段代码的目的是确保 IntegerCache 的范围在 [-128, 127] 内,因为这是 String 缓存这个优化策略可以应用的范围。如果 IntegerCache 的范围超出了这个范围,那么可能会导致无法进行这种优化,从而可能降低性能。

需要注意的是,assert 语句在默认情况下是关闭的,如果你想要开启它,需要在启动 JVM 时添加 -ea(或 -enableassertions)参数。

因此,当写 Integer i1 = 128;Integer i2 = 128; ,实际上在内存中创建了两个不同的对象,即使它们包含相同的值。

可以通过调用 equals() 方法来比较这两个对象的内容是否相同,而不是使用 == 运算符。这是因为 ==在比较对象时实际上是检查它们是否指向内存中的同一个对象,而不是比较它们的内容。而 equals() 方法则是比较对象的内容。

如果使用 equals() 方法来比较这两个对象,它们会被视为相等,因为它们包含相同的内容。但是,如果你使用 ==运算符来比较,它们会被视为不相等,因为它们是不同的对象。

2023-08-25
Contents

⬆︎TOP