Java中的奇怪整数拳击


114

我刚刚看到类似于以下代码:

public class Scratch
{
    public static void main(String[] args)
    {
        Integer a = 1000, b = 1000;
        System.out.println(a == b);

        Integer c = 100, d = 100;
        System.out.println(c == d);
    }
}

运行后,此代码块将打印出来:

false
true

我理解为什么第一个是false:因为两个对象是单独的对象,所以==比较引用。但是我不知道为什么第二条语句返回了true?当Integer的值在一定范围内时,会出现一些奇怪的自动装箱规则吗?这里发生了什么?



3
@RC-不是一个骗子,但讨论了类似的情况。感谢您的参考。
乔尔2010年

2
这太可怕了。这就是为什么我从不理解整个原语的意义,但是对象,但是两者,但是自动装箱,但是取决于,但是aaaaaaaaargh。
njzk2 2014年

1
@Razib:“自动装箱”一词是没有代码的,所以不要格式化它。
汤姆(Tom)

Answers:


102

true行实际上是由语言规范保证的。从5.1.7节开始

如果要装箱的值p是true,false,一个字节,\ u0000到\ u007f范围内的char或-128和127之间的int或short数字,则令r1和r2为任何两次装箱转换的结果的p。r1 == r2总是这样。

讨论继续进行,表明尽管保证了第二行的输出,但第一行没有保证(请参见下面引用的最后一段):

理想情况下,将给定的原始值p装箱将始终产生相同的参考。实际上,使用现有的实现技术可能不可行。以上规则是务实的妥协。上面的最后一个子句要求始终将某些通用值装在无法区分的对象中。该实现可以懒惰地或急切地缓存它们。

对于其他值,此公式不允许对程序员方面的带框值的身份进行任何假设。这将允许(但不要求)共享部分或全部这些引用。

这样可以确保在最常见的情况下,这种行为将是理想的行为,而不会造成不必要的性能损失,尤其是在小型设备上。内存限制较少的实现可能会例如缓存所有字符和短裤以及-32K-+ 32K范围内的整数和长整数。


17
还可能值得注意的是,自动装箱实际上只是用于调用valueOfbox类的方法(如Integer.valueOf(int))的语法糖。有趣的是,JLS使用intValue()et al 定义了准确的拆箱解包,但没有定义拆箱。
gustafc

@gustafc Integer除了通过官方publicAPI(即调用)之外,没有其他方法可以取消装箱intValue()。但是还有其他可能的方法来获取值的Integer实例int,例如,编译器可能会生成代码,以保留并重新使用先前创建的Integer实例。
Holger

31
public class Scratch
{
   public static void main(String[] args)
    {
        Integer a = 1000, b = 1000;  //1
        System.out.println(a == b);

        Integer c = 100, d = 100;  //2
        System.out.println(c == d);
   }
}

输出:

false
true

是的,产生第一输出用于比较参考;'a'和'b'-这是两个不同的参考。在第1点中,实际上创建了两个引用,它们类似于-

Integer a = new Integer(1000);
Integer b = new Integer(1000);

产生第二个输出是因为JVM当它Integer落入一个范围(从-128到127)时,它试图保存内存。在第2点,没有为'd'创建类型为Integer的新引用。它没有为整数类型引用变量“ d”创建新对象,而是仅分配了先前创建的由“ c”引用的对象。所有这些都是由JVM

这些内存保存规则不仅适用于Integer。为了节省内存,以下包装对象的两个实例(通过装箱创建)将始终为==,其中它们的原始值相同-

  • 布尔型
  • 字节
  • 字符从\ u0000\u007f(7f为127十进制)
  • 短整数从-128127

2
Long也具有与相同范围的缓存Integer
Eric Wang

8

某个范围(我认为可能是-128到127)之间的整数对象将被缓存并重新使用。超出此范围的整数每次都会获得一个新对象。


1
可以使用java.lang.Integer.IntegerCache.high属性扩展此范围。有趣的是Long没有那个选择。
Aleksandr Kravets 2015年

5

是的,当值在一定范围内时,会出现一个奇怪的自动装箱规则。将常量分配给Object变量时,语言定义中没有任何内容表明必须创建一个新对象。它可以重用缓存中的现有对象。

实际上,JVM通常会为此目的存储小型Integer的缓存以及诸如Boolean.TRUE和Boolean.FALSE的值。


4

我的猜测是,Java保留了一个已经被“装箱”的小整数的缓存,因为它们非常普遍,并且节省了重用现有对象而不是创建新对象的大量时间。


4

这是一个有趣的观点。在《有效的Java》一书中,建议始终重写自己类的equals。另外,要检查java类的两个对象实例是否相等,请始终使用equals方法。

public class Scratch
{
    public static void main(String[] args)
    {
        Integer a = 1000, b = 1000;
        System.out.println(a.equals(b));

        Integer c = 100, d = 100;
        System.out.println(c.equals(d));
    }
}

返回:

true
true

@Joel询问了其他话题,不是整数相等,而是对象运行时行为。
伊利亚·库兹涅佐夫

3

在Java中,对于整数,装箱在-128到127之间。在此范围内使用数字时,可以将其与==运算符进行比较。对于超出范围的Integer对象,必须使用equals。


3

将int文字直接分配给Integer引用是自动装箱的示例,其中,由编译器处理到对象转换代码的文字值。

因此,在编译阶段,编译器将转换Integer a = 1000, b = 1000;Integer a = Integer.valueOf(1000), b = Integer.valueOf(1000);

因此Integer.valueOf(),实际上是给我们整数对象的方法,如果我们看一下Integer.valueOf()方法的源代码,我们可以清楚地看到该方法在-128到127(含)范围内缓存整数对象。

/**
 *
 * 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);
 }

因此,如果传递的int文字大于-128且小于127 ,则Integer.valueOf()该方法从内部返回Integer 对象,而不是创建和返回新的整数对象IntegerCache

Java缓存这些整数对象,因为在日常编程中经常使用此整数范围,从而间接节省了一些内存。

当类由于静态块而被加载到内存中时,缓存在第一次使用时被初始化。高速缓存的最大范围可以由-XX:AutoBoxCacheMaxJVM选项控制。

对于整数对象只,类似于Integer.IntegerCache我们也有这缓存行为是不适用ByteCache, ShortCache, LongCache, CharacterCacheByte, Short, Long, Character分别。

您可以在我的文章Java Integer Cache-为什么Integer.valueOf(127)== Integer.valueOf(127)为True上阅读更多内容。


0

在Java 5中,引入了一项新功能,以节省内存并提高Integer类型对象处理的性能。整数对象在内部缓存,并通过相同的引用对象重用。

  1. 这适用于介于–127到+127(最大整数值)之间的整数值。

  2. 此整数缓存仅适用于自动装箱。使用构造函数构建整数对象时,将不会缓存它们。

有关更多详细信息,请通过下面的链接:

整数缓存的详细信息


0

如果我们检查Integerobeject 的源代码,我们将找到valueOf方法的源,如下所示:

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

这可以解释为什么在自动装箱期间Integer范围从-128(Integer.low)到127(Integer.high)的对象是相同的引用对象。我们可以看到有一个类IntegerCache负责Integer缓存数组,这是一个私有的静态内部Integer类。

还有一个有趣的例子可以帮助我们理解这种奇怪的情况:

public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {

      Class cache = Integer.class.getDeclaredClasses()[0]; 
      Field myCache = cache.getDeclaredField("cache"); 
      myCache.setAccessible(true);

      Integer[] newCache = (Integer[]) myCache.get(cache); 
      newCache[132] = newCache[133]; 

      Integer a = 2;
      Integer b = a + a;
      System.out.printf("%d + %d = %d", a, a, b); //The output is: 2 + 2 = 5    

}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.