【问题标题】:Weird Integer boxing in JavaJava中奇怪的整数拳击
【发布时间】:2010-06-28 05:43:30
【问题描述】:

我刚刚看到类似这样的代码:

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?当整数的值在一定范围内时,是否会出现一些奇怪的自动装箱规则?这是怎么回事?

【问题讨论】:

    标签: java autoboxing


    【解决方案1】:

    true 行实际上是由语言规范保证的。来自section 5.1.7

    如果被装箱的值p为真, false,一个字节,一个字符在范围内 \u0000 到 \u007f,或者一个 int 或 short 介于 -128 和 127 之间的数字,然后让 r1 和 r2 是任意两个的结果 p的拳击转换。它总是 r1 == r2 的情况。

    讨论继续进行,表明虽然您的第二行输出是有保证的,但第一行不是(请参阅下面引用的最后一段):

    理想情况下,装箱给定的原语 值 p,总是会产生 相同的参考。在实践中,这 使用现有的可能不可行 实施技术。规则 以上是务实的妥协。这 上面的最后条款要求 某些共同的价值观总是被装箱 变成无法区分的物体。这 实现可能会懒惰地缓存这些 或急切地。

    对于其他值,此公式 不允许任何关于 盒装值的标识 程序员的一部分。这将允许 (但不要求)分享一些或 所有这些参考资料。

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

    【讨论】:

      【解决方案2】:
      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”创建整数类型的新引用。它没有为整数类型引用变量“d”创建新对象,而是只分配了先前创建的由“c”引用的对象。所有这些都是由JVM 完成的。

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

      • 布尔值
      • 字节
      • \u0000\u007f的字符(7f是十进制的127)
      • -128127 的短整数

      【讨论】:

      • Long 还具有与Integer 相同范围的缓存。
      【解决方案3】:

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

      【讨论】:

      • 这个范围可以使用java.lang.Integer.IntegerCache.high属性扩展。有趣的是 Long 没有那个选项。
      【解决方案4】:

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

      事实上,JVM 通常会为此目的存储一个小整数的缓存,以及诸如 Boolean.TRUE 和 Boolean.FALSE 之类的值。

      【讨论】:

        【解决方案5】:

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

        【讨论】:

          【解决方案6】:

          这是一个有趣的观点。 在书中Effective 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
          

          【讨论】:

            【解决方案7】:

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

            所以在编译阶段编译器会将Integer a = 1000, b = 1000; 转换为Integer a = Integer.valueOf(1000), b = Integer.valueOf(1000);

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

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

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

            Java 缓存这些整数对象,因为这个整数范围在日常编程中被大量使用,间接节省了一些内存。

            当类因为静态块而加载到内存中时,缓存在第一次使用时被初始化。缓存的最大范围可以通过-XX:AutoBoxCacheMax JVM 选项来控制。

            这种缓存行为仅适用于 Integer 对象,类似于 Integer.IntegerCache 我们也有ByteCache, ShortCache, LongCache, CharacterCache 分别对应Byte, Short, Long, Character

            你可以在我的文章Java Integer Cache - Why Integer.valueOf(127) == Integer.valueOf(127) Is True阅读更多。

            【讨论】:

              【解决方案8】:

              整数缓存是 Java 版本 5 中引入的一项功能,主要用于:

              1. 节省内存空间
              2. 性能提升。
              Integer number1 = 127;
              Integer number2 = 127;
              
              System.out.println("number1 == number2" + (number1 == number2); 
              

              输出: True


              Integer number1 = 128;
              Integer number2 = 128;
              
              System.out.println("number1 == number2" + (number1 == number2);
              

              输出: False

              怎么做?

              实际上,当我们为 Integer 对象赋值时,它会在后台自动提升

              Integer object = 100;
              

              其实是在调用Integer.valueOf()函数

              Integer object = Integer.valueOf(100);
              

              valueOf(int) 的具体细节

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

              说明:

              此方法将始终缓存 -128 到 127 范围内的值, 包括在内,并且可能会缓存此范围之外的其他值。

              当需要 -128 到 127 范围内的值时,它每次都会返回一个恒定的内存位置。 但是,当我们需要一个大于 127 的值时

              return new Integer(i);

              每次我们启动一个对象时都会返回一个新的引用。


              此外,Java 中的== 运算符用于比较两个内存引用而不是值。

              Object1 位于 1000 并包含值 6。
              Object2 位于 1020 并包含值 6。

              Object1 == Object2False,因为它们具有不同的内存位置,但包含相同的值。

              【讨论】:

                【解决方案9】:

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

                【讨论】:

                  【解决方案10】:

                  如果我们查看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);
                  }
                  

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

                  还有一个有趣的例子可能有助于理解这种奇怪的情况:

                  public static void main(String[] args) throws ReflectiveOperationException {
                      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
                  }
                  

                  【讨论】:

                    【解决方案11】:

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

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

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

                    更多详情请点击以下链接:

                    Integer Cache in Detail

                    【讨论】:

                      【解决方案12】:

                      Integer 类包含 -128 和 127 之间值的缓存,这是 JLS 5.1.7. Boxing Conversion 所要求的。因此,当您使用== 来检查此范围内的两个Integers 的相等性时,您会得到相同的缓存值,如果您比较此范围外的两个Integers,则会得到两个不同的值。

                      可以通过改变JVM参数来增加缓存上限:

                      -XX:AutoBoxCacheMax=<cache_max_value>
                      

                      -Djava.lang.Integer.IntegerCache.high=<cache_max_value>
                      

                      见内部IntegerCache类:

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

                      【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 1970-01-01
                        • 2012-04-17
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2010-09-15
                        • 1970-01-01
                        相关资源
                        最近更新 更多