听到这个问题,你如果傻乎乎的直接回答:一个值是 127,一个是 128,就会显得理解问题太过于表面化了,那么很可能就会直接让你出门右转了 😂。
这个问题比较简单,面试官首先是想考察对 Java 基础知识的理解,然后也是考察对 Java 底层逻辑中内存管理及性能优化设计的了解。
这个问题其实可以按照下面这么回答:
new Integer("127")
:总是创建一个新的Integer
对象。该行语句使用了new
关键字,new
关键字的作用是在堆内存分配空间创建对象实例。构造方法中即使传递相同的字符串值,每次调用也都会产生一个新的对象实例。Integer.valueOf("128")
:优先从内部缓存中获取对象。使用valueOf()
方法获取Integer
时,会用到其内部的缓存机制,如果数值落在 -128 到 127 的范围内,则返回缓存中的已有实例;否则,创建新的对象。
这里,这两行语句都是创建了一个新的 Integer
对象,127 在缓存范围内,但是使用了 new
关键字后绕过了缓存;128 直接不在缓存范围,所以也是创建新对象。开发中更推荐使用 Integer.valueOf()
这种方式,因为它可能用到内部缓存,从而提高性能和节省内存。
从 valueOf 方法看 Integer 内部缓存实现原理
Integer.valueOf(String s)
是 Integer
类提供的一个静态方法,用于将字符串转换为 Integer
对象。与直接使用构造函数相比,这种方法更值得推荐,因为当传入的字符串所表示的数值在特定范围之内(默认是从 -128 到 127),valueOf()
方法会从内部缓存中返回一个已有的对象实例;而如果数值超出了这个范围,才会创建一个新的 Integer
对象。
通过这种方式,valueOf()
方法不仅减少了不必要的对象创建,节省了内存,还提高了性能,尤其是在频繁进行数值转换的场景下。
public static Integer valueOf(int i) {
// 判断传入的数值是否在 IntegerCache 的缓存范围内
if (i >= IntegerCache.low && i <= IntegerCache.high)
// 命中缓存直接取缓存值返回
return IntegerCache.cache[i + (-IntegerCache.low)];
// 否则创建一个新的对象返回
return new Integer(i);
}
IntegerCache 机制
Integer
的内部缓存是依赖其静态内部类 IntegerCache
实现的,该类预先创建并缓存了一组常用整数值的对象数组。这一设计确保了在程序运行期间,当需要频繁使用这些特定范围内的整数时,可以直接从缓存中获取已存在的 Integer
实例,而无需每次都创建新的对象。这种设计,使得 Java 确保了小整数的高效复用。
private staticclass IntegerCache {
// 缓存范围的下限,默认为 -128
staticfinalint low = -128;
// 缓存范围的上限,默认为 127,但可以通过系统属性配置
staticfinalint high;
// 内部缓存池,预先缓存的常用整数对象存储在这里
staticfinal Integer cache[];
static {
// 高值可按属性配置,默认127
int h = 127;
// 从系统属性 "java.lang.Integer.IntegerCache.high" 获取用户自定义的高值
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 在合理的范围内,且缓存池数组大小不会超出 Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// 处理异常情况,如果属性设置的值无法解析为 int,忽略。
}
}
high = h;
// 初始化缓存数组,根据确定的高低值计算出缓存数组的正确大小,确保它可以容纳所有的缓存整数值
cache = new Integer[(high - low) + 1];
int j = low;
// 创建新的 Integer 对象,并将它们放入缓存中
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// 确保缓存中的最高值至少为 127
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
示例演示
public class IntegerExample {
public static void main(String[] args) {
Integer a = new Integer("127");
Integer b = new Integer("127");
System.out.println(a == b); // false: 不同对象
System.out.println(a.equals(b)); // true: 同样的值
Integer c = Integer.valueOf("127");
Integer d = Integer.valueOf("127");
System.out.println(c == d); // true: 缓存中的同一对象
System.out.println(c.equals(d)); // true: 同样的值
Integer e = Integer.valueOf("128");
Integer f = Integer.valueOf("128");
System.out.println(e == f); // false: 不同对象
System.out.println(e.equals(f)); // true: 同样的值
}
}
在这个例子中,我们可以看到:
new Integer("127")
创建的两个对象虽然具有相同的值,但由于使用了new
关键字,它们在堆内存上分配的地址是不同的,因此 a == b 返回 false。Integer.valueOf("127")
由于使用了缓存机制,c 和 d 实际指向的是缓存池中的同一个对象,所以 c == d 返回 true。而 Integer.valueOf("128")
由于超出缓存范围的值,即使使用了valueOf()
方法,其内部也是通过new
关键字创建的新对象,因此 e == f 也是 false。
为什么是缓存范围 -128 到 127
字节范围:Java 中的一个字节( byte
)正好是 -128 到 127 这个整数范围。使用一个字节来表示这个缓存范围是合理的,因为它是计算机中最基本的数据单位,易于处理且占用空间小。内存权衡:缓存范围不能太大,否则会占用过多内存。-128 到 127 这个范围在大多数情况下已经足够覆盖日常编程中常见的小整数值。同时,这个范围也相对较小,不会对 JVM 的运行时性能造成显著影响。 性能优化:这个范围内的整数基本满足循环计数器、数组索引等场景。通过缓存这些常用值,可以显著减少对象创建次数,从而提高程序执行效率并减轻了垃圾回收的压力。
只能是 -128 到 127 吗
不是,缓存的大小可以通过 JVM 参数 -XX:AutoBoxCacheMax=<size>
或者 java.lang.Integer.IntegerCache.high
属性来动态调整。需要注意的是,调整的都是缓存池的上限,下限 -128 是固定的,不能改变。
-XX:AutoBoxCacheMax=<size>
与java.lang.Integer.IntegerCache.high
的区别
-XX:AutoBoxCacheMax=<size>
是一个 JVM 级别的参数,适用于所有基于该 JVM 运行的应用程序;java.lang.Integer.IntegerCache.high
是特定于某个应用实例的配置,允许更加细粒度地控制。
参数用法示例
-XX:AutoBoxCacheMax=<size>
java -XX:AutoBoxCacheMax=200 -jar app.jar
java.lang.Integer.IntegerCache.high
它是个系统属性,我们通过命令行或在程序内部对其值进行设定,比如:
System.setProperty("java.lang.Integer.IntegerCache.high", "200");
或者在启动 JVM 时通过 -D 选项传递:
java -Djava.lang.Integer.IntegerCache.high=200 -jar app.jar
结语
虽然了解了 Integer
内部缓存的实现细节,但在开发中也不可过度依赖。要牢记 == 比较的是对象引用,而 equals()
比较的是对象的内容。对于包装类,除非明确知道两个变量指向同一个对象,否则应当使用 equals()
。
您的鼓励对我持续创作非常关键,如果本文对您有帮助,请记得点赞、分享、在看哦~~~谢谢!