美团面试:new Integer("127") 和 Integer.valueOf("128") 有什么区别

文摘   2025-01-20 09:36   山东  

听到这个问题,你如果傻乎乎的直接回答:一个值是 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()

您的鼓励对我持续创作非常关键,如果本文对您有帮助,请记得点赞、分享、在看哦~~~谢谢!

Java驿站
这里是【Java驿站】,一个Java编程学习与交流平台。
 最新文章