【问题标题】:Why aren't Integers cached in Java?为什么 Java 中不缓存整数?
【发布时间】:2011-07-13 18:26:41
【问题描述】:

我知道这个话题有similar posts,但他们并没有完全解决我的问题。当你这样做时:

Integer a = 10;
Integer b = 10;
System.out.println("a == b: " + (a == b));

这将(显然)大部分时间打印true,因为[-128, 127] 范围内的整数以某种方式被缓存。但是:

Integer a = new Integer(10);
Integer b = new Integer(10);
System.out.println("a == b: " + (a == b));

将返回false。我知道我要求的是 Integer 的新实例,但是由于盒装原语在 Java 中是不可变的,并且机器已经可以做“正确的事情”(如第一种情况所示),为什么会发生这种情况?

如果所有带有 10 的 Integer 实例都是内存中的同一个对象,岂不是更有意义?换句话说,为什么我们没有类似于“String interning”的“Integer interning”?

更好的是,如果表示同一事物的装箱原语的实例不管值(和类型)是同一个对象,岂不是更有意义?或者至少正确回复==

【问题讨论】:

  • 我不同意,我认为以这种方式行事是对实际情况的歪曲,我实际上认为整数缓存和 String '==' 的实现不应该成为核心的一部分同样的原因,不可否认,这篇文章中确定的问题似乎不一致。
  • 虽然绝不是重复的,但我在此处的回答中说明了此处所关心的大部分内容:stackoverflow.com/questions/5199359/…
  • 当前行为与String一致,其中常量将被实习,但你这样做new String("foo")你总是会得到一个新实例。
  • @jtahlborn 只是部分一致,因为更大的整数根本不会被“实习”。
  • 我指的是“new Foo()”,而不是常量版本。是的,我意识到并不是所有的常量都是实习的,但最初的问题是关于构造函数的显式使用。

标签: java equality autoboxing


【解决方案1】:

应该很清楚,缓存对性能的影响是不可接受的——每次创建整数时都会增加一个 if 语句和内存查找。仅此一项就掩盖了任何其他原因以及该线程上其余的痛苦。

就“正确”地响应 == 而言,OP 错误地假设了正确性。整数确实通过一般 Java 社区对正确性的期望以及规范对正确性的定义正确响应 ==。也就是说,如果两个引用指向同一个对象,它们就是==。如果两个引用指向不同对象,即使它们具有相同的内容,它们也不是==。因此,new Integer(5) == new Integer(5) 的计算结果为 false 也就不足为奇了。

更有趣的问题是为什么每次都需要new Object(); 来创建一个唯一的实例?一世。 e.为什么new Object(); 不允许缓存?答案是wait(...)notify(...) 调用。缓存 new Object()s 会错误地导致线程在不应同步时相互同步。

如果不是这样,那么 Java 实现完全可以用单例缓存 new Object()s。

这应该可以解释为什么必须要求 new Integer(5) 完成 7 次才能创建 7 个唯一的 Integer 对象,每个对象都包含值 5(因为 Integer 扩展了 Object)。


次要的,不太重要的东西: 这个原本不错的方案中的一个问题是自动装箱和自动拆箱功能造成的。如果没有该功能,您将无法进行比较,例如new Integer(5) == 5。为了启用这些,Java 取消装箱 对象(并且 装箱原语)。因此new Integer(5) == 5 被转换为:new Integer(5).intValue() == 5(并且不是 new Integer(5) == new Integer(5)

最后要理解的是n 的自动装箱不是new Integer(n) 完成的。它是通过调用Integer.valueOf(n) 在内部完成的。

如果你认为自己理解并想要测试自己,请预测以下程序的输出:

public class Foo {
  public static void main (String[] args) {
    System.out.println(Integer.valueOf(5000) == Integer.valueOf(5000));
    System.out.println(Integer.valueOf(5000) == new Integer(5000));
    System.out.println(Integer.valueOf(5000) == 5000);
    System.out.println(new Integer(5000) == Integer.valueOf(5000));
    System.out.println(new Integer(5000) == new Integer(5000));
    System.out.println(new Integer(5000) == 5000);
    System.out.println(5000 == Integer.valueOf(5000));
    System.out.println(5000 == new Integer(5000));
    System.out.println(5000 == 5000);
    System.out.println("=====");
    System.out.println(Integer.valueOf(5) == Integer.valueOf(5));
    System.out.println(Integer.valueOf(5) == new Integer(5));
    System.out.println(Integer.valueOf(5) == 5);
    System.out.println(new Integer(5) == Integer.valueOf(5));
    System.out.println(new Integer(5) == new Integer(5));
    System.out.println(new Integer(5) == 5);
    System.out.println(5 == Integer.valueOf(5));
    System.out.println(5 == new Integer(5));
    System.out.println(5 == 5);
    System.out.println("=====");
    test(5000, 5000);
    test(5, 5);
  }
  public static void test (Integer a, Integer b) {
    System.out.println(a == b);
  }
}

如果所有== 都更改为.equals(...),则还要预测输出

更新:感谢用户 @sactiw 的评论:“缓存的默认范围是 -128 到 127,从 java 1.6 开始,您可以通过传递 -XX:AutoBoxCacheMax= 重置上限 >=127从命令行”

【讨论】:

  • 性能损失已经存在,因为缓存了较小的整数 。是的,== 的正确性取决于定义。我在这里争论的是,没有理由为什么两个具有相同值的 Integers 在 == 比较时返回 false。
  • 顺便说一句,这里的一些“痛苦”是因为我最近花了一些时间在 C++ 中编码,在那里你可以重载运算符(例如:==)。啊,如果这在 Java 中是可能的。
  • 我们在交叉评论 :-) 我碰巧也是一个不错的前 c++ 程序员。一方面,它应该让您更容易理解在 java == 中始终是指针比较。是的,不能重载运算符令人痛苦,但总的来说,我觉得这是一个加分项,因为我可以阅读 Java 代码的孤立片段,并且可以确定运算符在做什么。祝你好运!
  • @no_answer_not_upvoted:Java 重载 == 用于基元的值比较和其他所有内容的引用比较,如果禁止在引用类型和基元之间进行比较,这种设计可能还可以,但如果混合比较则变得可疑是允许的[我个人认为== 应该禁止所有 混合比较,除了那些只涉及整数基元,或者特别涉及double 和非long 整数基元]。给定int i=2; Integer I1=new Integer(i); Integer I2=new Integer(i);== 现在实现了一个损坏的等价关系。
  • @supercat 我已经更新了答案以解决您的问题。据我所知,== 运算符没有重载。发生的情况是 Java 在与原语比较之前将 Integer 拆箱。因此等价关系并没有真正被打破;域不同。
【解决方案2】:

这可能会破坏在此设计更改之前编写的代码,当时每个人都正确地假设两个新创建的实例是不同的实例。可以为自动装箱做,因为以前没有自动装箱,但是改变新的含义太危险了,可能不会带来太大的收益。在 Java 中,短期对象的成本并不高,甚至可能低于维护长期对象缓存的成本。

【讨论】:

  • +1 就这么简单。简单的旧向后兼容性。
  • 是的,但我想不出基于参考来比较两个盒装基元的情况。换句话说,如果a == b 都是Integer(10),那么什么时候才有意义?
  • @NullUserException,你的论点本质上是整数上的 == 应该返回整数是否相等。我同意。但这是运算符重载的一个论点,而不是整数对象的缓存。
  • @NullUserException:需要保存一堆身份令牌的代码,每个身份令牌都分配了一个数值,可以为此目的使用Integer[](或Long[],或其他)。最好定义一个包含适当数字基元字段的 SequencedLockingToken 类,然后使用SequencedLockingToken 类,但如果它们是用new 构造的,则使用盒装基元作为身份标记是合法的。跨度>
【解决方案3】:

如果你检查你看到的来源:

/**
 * Returns an Integer instance representing the specified int value. If a new
 * Integer instance is not required, this method should generally be used in
 * preference to the constructor Integer(int), as this method is likely to
 * yield significantly better space and time performance by caching frequently
 * requested values.
 * 
 * @Parameters: i an int value.
 * @Returns: an Integer instance representing i.
 * @Since: 1.5
 */
 public static Integer valueOf(int i) {
      final int offset = 128;
      if (i >= -128 && i <= 127) { // must cache
          return IntegerCache.cache[i + offset];
      }
      return new Integer(i);
 }

来源:link

这是== 使用整数返回布尔值 true 的性能原因——这完全是一个 hack。如果你想比较值,那么你有 comparetoequals 方法。

在其他语言中,例如你也可以使用==来比较字符串,这基本上是相同的原因,被称为java语言的最大不幸之一。

int 是一种原始类型,由语言预定义并由保留关键字命名。作为一个原语,它不包含类或任何与类相关的信息。 Integer 是一个不可变的原始类,它通过包私有、本机机制加载并转换为 Class - 它提供自动装箱并在 JDK1.5 中引入。 JDK1.5 之前的intInteger 有两个非常不同的东西。

【讨论】:

    【解决方案4】:

    在Java 中,每次调用new 运算符时,都会分配新内存并创建一个新对象。这是标准的语言行为,据我所知,没有办法绕过这种行为。即使是标准班也必须遵守这条规则。

    【讨论】:

    • IDK,Java 确实为一些标准类提供了一些特殊机制,例如:原始包装器的自动装箱,String 进行实习并响应 + 运算符。所以这可以内置到语言中。
    • 是的,这本来可以,但事实并非如此。 new 的语义始终是一致的:创建一个新对象。
    • @NullUserException:是的,但这些示例没有使用new 关键字。
    【解决方案5】:

    据我了解,new 无论如何都会创建一个新对象。这里的操作顺序是先调用new,它实例化一个新对象,然后调用构造函数。 JVM没地方干预,把new变成“根据传入构造函数的值抓取一个缓存的Integer对象”。

    顺便说一句,你考虑过Integer.valueOf吗?这行得通。

    【讨论】:

    • 我知道如何让它发挥作用;我只是想知道为什么语言中没有内置更有效的解决方案,因为这些对象是不可变的。
    • 这可能是设计使然 - 这个想法是 new 暗示您想要创建一个新对象,可能是因为您想要两个具有相同整数的 Integer 对象,如果您比较它们将不会返回 true通过==。只是为了让程序员可以选择这样做。
    【解决方案6】:

    如果所有带有 10 的 Integer 实例都是内存中的同一个对象,岂不是更有意义?换句话说,为什么我们没有类似于“String interning”的“Integer interning”?

    因为那会很糟糕!

    首先,这段代码会抛出一个OutOfMemoryError:

    for (int i = 0; i <= Integer.MAX_VALUE; i++) {
        System.out.printf("%d\n", i);
    }
    

    大多数 Integer 对象可能是短暂的。

    其次,您将如何维护这样一组规范的 Integer 对象?用某种表格或地图。您将如何仲裁对该地图的访问?带有某种锁定。所以突然之间,自动装箱将成为线程代码的性能扼杀同步噩梦。

    【讨论】:

    • 它不会抛出 OutOfMemoryErrory,他只是提议缓存小值。在这种情况下,您会将 Integer 对象保存在不需要任何同步的数组中。
    • @Winston Ewert,许多其他人的回答是关于 Java 的 new 关键字的语义。我正在回应一般实习整数的想法(正如我所引用的)。小值已经缓存了,您只需要使用正确的 API(即Integer.valueOf(int))。所以我就为什么我认为实习大值会很愚蠢提出了自己的看法。
    • 您的回答做出了错误的假设,即实习意味着所有对象都必须永远留在内存中。由于问题已经说“类似于'String interning'”,您可以简单地与for(int i = 0; i &lt;= Integer.MAX_VALUE; i++) System.out.println(String.valueOf(i).intern());进行比较,它运行时不会抛出OutOfMemoryError
    【解决方案7】:

    您的第一个示例是规范的副产品,要求在 0 左右的某个范围内创建享元。永远不应依赖它。

    至于为什么 Integer 不像 String 那样工作?我会想象避免开销到已经很慢的过程。尽可能使用原语的原因是它们速度更快,占用的内存更少。

    现在更改它可能会破坏现有代码,因为您正在更改 == 运算符的功能。

    【讨论】:

      【解决方案8】:

      顺便说一句,如果你这样做了

      Integer a = 234345;
      Integer b = 234345;
      
      if (a == b) {}
      

      这可能是真的。

      这是因为由于您没有使用 new Integer(),因此允许 JVM(不是类代码)在认为合适的情况下缓存它自己的 Integers 副本。现在您不应该基于此编写代码,但是当您说 new Integer(234345) 时,规范保证您肯定会有不同的对象。

      【讨论】:

      • 这也是让我感到困扰的另一个原因,因为它是一个依赖于实现的东西,它增加了所有这一切的不一致性。
      • @MeBigFatGuy 这在 java 1.6 及更高版本中是可能的,您可以通过传递 -XX:AutoBoxCacheMax= 将上限重置为 >=127 但在 java 1.5 中不可能,因为在 java 1.5 缓存范围是固定的,即仅 -128 到 127 -或者-我在这里遗漏了什么吗?
      • 我的回答与整数缓存无关。如果 JVM 认为合适的话,它可以优化整数装箱,而不管实际值如何。因此,如果您在代码中多次使用值 165234234,则允许 JVM 缓存该装箱原语。现在你永远不会知道这是否真的发生在你身上,但它可以。这只是增加了比较盒装图元的“明显脆弱性”。所以不要这样做。
      【解决方案9】:

      new 表示new

      new Object() 并不轻浮。

      【讨论】:

        【解决方案10】:

        一个新的实例就是一个新的实例,所以它们在值上是相等的,但它们作为对象是不相等的。

        所以a == b 不能返回true

        如果它们是 1 个对象,正如您所要求的:a+=2; 会将 2 添加到所有 int = 10 - 这太糟糕了。

        【讨论】:

        • 没有。 a+= 2 类似于 a = Integer.valueOf(a.intValue() + 2)。你得到另一个 Integer 实例。整数是不可变的。它的价值永远不会改变。
        • 我想你们俩都是对的,如果你使用'new',你总是会得到新的实例,但 Integer 是不可变的类,你不能修改它,因此如果你尝试像 =一个 + 2;您将获得另一个具有更新值的实例。这也适用于存在于缓存中的整数(例如来自像 Integer x = 5 这样的初始化)
        【解决方案11】:

        让我通过提供指向 JLS 相关部分的链接,稍微扩展一下 ChrisJ 和 EboMike 的答案。

        new 是 Java 中的关键字,允许在 类实例创建表达式 (Section 15.9 of the JLS) 中使用。这与 C++ 不同,其中 new 是一个运算符,可以重载。

        表达式 always 尝试分配内存,并在每次评估时产生一个新对象 (Section 15.9.4)。所以到那时,缓存查找已经太迟了。

        【讨论】:

          【解决方案12】:

          假设您准确地描述了代码的行为,听起来自动装箱不适用于“gets”(=)操作符,而是听起来像 Integer x = 10;给对象 xa 内存指针 '10' 而不是 10 的值。因此 ((a == b) == true)( 将评估为真,因为 == on objects 对您分配给 10 的内存地址进行操作.

          那么什么时候应该使用自动装箱和拆箱呢?仅当引用类型和原语之间存在“阻抗不匹配”时才使用它们,例如,当您必须将数值放入集合中时。对于科学计算或其他对性能敏感的数字代码,不适合使用自动装箱和拆箱。 Integer 不能替代 int;自动装箱和拆箱模糊了原始类型和引用类型之间的区别,但它们并没有消除它。

          What oracle has to say on the subject.

          请注意,该文档未提供任何带有 '=' 运算符的示例。

          【讨论】:

          • 这不是真的。这不是 C,Java 中没有指针的概念。自动装箱 在第一种情况下正常工作。
          • 我最近花了很多时间在内核中挖掘,你确定它没有传递 int '10' 的地址吗?我猜它不会抛出类型异常这一事实表明了功能性自动装箱。
          【解决方案13】:

          对于Integer 对象,使用a.equals(b) 条件进行比较。

          编译器不会在您比较时为您拆箱,除非您将值分配给基本类型。

          【讨论】:

          • 我知道;这不是我的问题。
          • 我猜你的标题应该是“为什么没有为整数定义 intern()?”
          【解决方案14】:

          还请注意,Java 1.5 中的缓存范围是 -128 到 127,但 Java 1.6 以后它是默认范围,即您可以通过传递 -XX:AutoBoxCacheMax=new_limit来设置上限 >= 127 > 从命令行

          【讨论】:

            【解决方案15】:

            这是因为您使用new 语句来构造对象。

            Integer a = Integer.valueOf(10);
            Integer b = Integer.valueOf(10);
            System.out.println("a == b: " + (a == b));
            

            这将打印出true。很奇怪,不过是 Java。

            【讨论】:

            • 规范要求虚拟机在 0 左右的某个范围内创建享元。这就是为什么这样做有效,但它应该永远被使用。
            • 那是使用 [-128, 127] 的缓存范围的地方,对于 OP 的第一个示例,不是。所以(500 == 500) -&gt; true,但是(Integer.ValueOf(500) == Integer.ValueOf(500)) -&gt; false
            • 实际上,规范允许 JVM 缓存更多。它只需要 [-128,127]。这意味着在一个 JVM 上,Integer.valueOf(500) == Integer.valueOf(500) 可能会返回 true,但在大多数情况下它会返回 false。这可能会引入一个几乎永远不会被追踪到的错误。
            • @glowcoder - 完全正确。它实际上比指定为 [-128,127] 更糟糕
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-03-09
            • 2021-12-09
            • 2016-11-24
            相关资源
            最近更新 更多