为什么 Java 中不缓存整数?

发布于 2024-10-22 02:30:55 字数 748 浏览 1 评论 0原文

我知道关于该主题有类似的帖子,但他们并没有完全解决我的问题。当你这样做时:

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 的所有实例都是内存中的同一个对象,不是更有意义吗?换句话说,为什么我们没有类似于“字符串实习”的“整数实习”?

更好的是,如果代表同一事物的装箱基元的实例无论值(和类型)如何,都是同一个对象,这不是更有意义吗?或者至少正确响应==

I know there are similar posts on the topic, but they don't quite address my question. When you do:

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

This will (apparently) print true most of the time because integers in the range [-128, 127] are somehow cached. But:

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

Will return false. I understand that I am asking for new instances of an Integer, but since boxed primitives are immutable in Java, and the machinery is already there to do the "right thing" (as seen in the first case), why does this happen?

Wouldn't it make more sense if all instances of an Integer with a 10 be the same object in memory? In other words, why don't we have "Integer interning" which would be similar to "String interning"?

Better yet, wouldn't it make more sense if instances of a boxed primitive representing the same thing, regardless of value (and type), be the same object ? Or at least respond correctly to ==?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(15

愁以何悠 2024-10-29 02:30:55

应该非常清楚的是,缓存会对性能产生不可接受的影响——每次创建 Integer 时都会有额外的 if 语句和内存查找。仅此一点就掩盖了任何其他原因以及该线程上的其他痛苦。

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

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

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

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


次要的、不太重要的东西:这个本来很好的方案中的一个问题是由自动装箱和自动拆箱功能引起的。如果没有该功能,您将无法进行诸如 new Integer(5) == 5 之类的比较。为了启用这些功能,Java 会取消装箱对象(并且装箱原语)。因此,new Integer(5) == 5 转换为:new Integer(5).intValue() == 5(并且不是 < code>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”

It should be very clear that caching has an unacceptable performance hit -- an extra if statement and memory lookup every time you create an Integer. That alone overshadows any other reason and the rest of the agonizing on this thread.

As far as responding "correctly" to ==, the OP is mistaken in his assumption of correctness. Integers DO respond correctly to == by the general Java community's expectation of correctness and of course by the specification's definition of correctness. That is, if two references point to the same object, they are ==. If two references point to different objects, they are not == even if they have the same contents. Thus, it should be no surprise that new Integer(5) == new Integer(5) evaluates to false.

The more interesting question is why new Object(); should be required to create a unique instance every time? i. e. why is new Object(); not allowed to cache? The answer is the wait(...) and notify(...) calls. Caching new Object()s would incorrectly cause threads to synchronize with each other when they shouldn't.

If it were not for that, then Java implementations could totally cache new Object()s with a singleton.

And that should explain why new Integer(5) done 7 times must be required to create 7 unique Integer objects each containing the value 5 (because Integer extends Object).


Secondary, Less Important Stuff: One problem in this otherwise nice scheme results from the autoboxing and autounboxing feature. Without the feature you could not do comparisons such as new Integer(5) == 5. To enable these, Java unboxes the object (and does not box the primitive). Therefore new Integer(5) == 5 is converted to: new Integer(5).intValue() == 5 (and not new Integer(5) == new Integer(5).

One last thing to understand is that autoboxing of n is not done by new Integer(n). It is done internally by a call to Integer.valueOf(n).

If you think you understand and want to test yourself, predict the output of the following program:

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

For extra credit, also predict the output if all the == are changed to .equals(...)

Update: Thanks to comment from user @sactiw : "default range of cache is -128 to 127 and java 1.6 onward you can reset the upper value >=127 by passing -XX:AutoBoxCacheMax= from command line"

季末如歌 2024-10-29 02:30:55

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

This would potentially break code written before this design change, when everybody righfully assumed that two newly created instances were different instances. It could be done for autoboxing, because autoboxing didn't exist before, but changing the meaning of new is too dangerous, and probably doesn't bring much gain. The cost of short-lived objects is not big in Java, and could even be lower than the cost of maintaining a cache of long-lived objects.

江湖彼岸 2024-10-29 02:30:55

如果您检查源代码,您会看到:

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

Source: link

这是 == 使用整数返回 boolean true 的性能原因 - 这完全是一个 hack。如果您想比较值,那么您可以使用 comparetoequals 方法。

在其他语言中,比如也可以使用 == 来比较字符串,基本道理是一样的,被称为java语言最大的硬伤之一。

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

If you check the source you see:

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

Source: link

It's the performance reasons why == returns boolean true with integers - it is totally a hack. If you want to compare values, then for that you have compareto or equals method.

In other languages, for example you can use == to compare strings as well, it is basically the same reason and it is called as one of the biggest mishaps of java language.

int is a primitive type, predefined by the language and named by a reserved keyword. As a primitive it does not contain class or any class associated information. Integer is an immutable primitive class, that is loaded through a package-private, native mechanism and casted to be Class - this provides auto boxing and was introduced in JDK1.5. Prior JDK1.5 int and Integer where 2 very different things.

梦里兽 2024-10-29 02:30:55

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

In Java, every time you call the new operator, you allocate new memory and you create a new object. That's standard language behavior, and to my knowledge there is no way to bypass this behavior. Even standard classes have to abide by this rule.

梦归所梦 2024-10-29 02:30:55

据我了解,无论如何,new都会创建一个新对象。这里的操作顺序是,您首先调用 new,它实例化一个新对象,然后调用构造函数。 JVM 没有地方可以干预并将 new 转换为“根据传递给构造函数的值抓取缓存的 Integer 对象”。

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

It is my understanding that new will create a new object, no matter what. The order of operations here is that you first call new, which instantiates a new object, then the constructor gets called. There is no place for the JVM to intervene and turn the new into a "grab a cached Integer object based on the value passed into the constructor".

Btw, have you considered Integer.valueOf? That works.

情绪失控 2024-10-29 02:30:55

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

因为那会很糟糕!

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

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

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

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

Wouldn't it make more sense if all instances of an Integer with a 10 be the same object in memory? In other words, why don't we have "Integer interning" which is similar to "String interning"?

Because it would be awful!

First, this code would throw an OutOfMemoryError:

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

Most Integer objects are probably short-lived.

Second, how would you maintain such a set of canonical Integer objects? With some kind of table or map. And how would you arbitrate access to that map? With some kind of locking. So suddenly autoboxing would become a performance-killing synchronization nightmare for threaded code.

吃颗糖壮壮胆 2024-10-29 02:30:55

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

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

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

Your first example is a byproduct of the spec requiring that flyweights be created in a certain range around 0. It should never, ever, be relied on.

As for why Integer doesn't work like String ? I would imagine avoiding overhead to an already slow process. The reason you use primitives where you can is because they are significantly faster and take up way less memory.

Changing it now could break existing code because you're changing the functionality of the == operator.

音栖息无 2024-10-29 02:30:55

顺便说一句,如果你

Integer a = 234345;
Integer b = 234345;

if (a == b) {}

这样做,这可能是真的。

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

BTW, If you do

Integer a = 234345;
Integer b = 234345;

if (a == b) {}

it is possible that this will be true.

This is because since you didn't use new Integer(), the JVM (not the class code) is allowed to cache its own copies of Integers if it sees fit. Now you shouldn't write code based on this, but when you say new Integer(234345) you are guaranteed by the spec that you will definitely have different objects.

浪荡不羁 2024-10-29 02:30:55

new 表示

new Object() 并不是无意义的。

new means new.

new Object() isn't frivolous.

要走就滚别墨迹 2024-10-29 02:30:55

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

所以a == b不能返回true

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

A new instance is a new instance, so they are equal in value, but they are not equal as objects.

So a == b can't return true.

If they were 1 object, as you ask for: a+=2; would add 2 to all int = 10 - that would be awful.

微凉徒眸意 2024-10-29 02:30:55

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

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

表达式总是尝试分配内存,并在每次计算时生成一个新的对象(第 15.9.4 节)。因此此时进行缓存查找已经为时已晚。

Let me just expand slightly on ChrisJ's and EboMike's answers by giving links to the relevant sections of the JLS.

new is a keyword in Java, allowed in class instance creation expressions (Section 15.9 of the JLS). This is different from C++, where new is an operator and can be overloaded.

The expression always tries to allocate memory, and yields a fresh object each time it is evaluated (Section 15.9.4). So at that point it's already too late for cache lookup.

吾家有女初长成 2024-10-29 02:30:55

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

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

Oracle 对于这个主题有什么说法。

请注意,文档没有提供任何带有“=”运算符的示例。

Assuming your describing the behavior of you code accurately it sounds like autoboxing isn't working on the 'gets' (=) operatior, instead it sounds like Integer x = 10; gives the object x a memory pointer of '10' instead of a vale of 10. Therefore ((a == b) == true)( will evaluate to true because == on objects operates on the memory addresses which you assigned both to 10.

So when should you use autoboxing and unboxing? Use them only when there is an “impedance mismatch” between reference types and primitives, for example, when you have to put numerical values into a collection. It is not appropriate to use autoboxing and unboxing for scientific computing, or other performance-sensitive numerical code. An Integer is not a substitute for an int; autoboxing and unboxing blur the distinction between primitive types and reference types, but they do not eliminate it.

What oracle has to say on the subject.

Notice that the documentation doesn't supply any examples with the '=' operator.

想你的星星会说话 2024-10-29 02:30:55

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

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

For Integer objects use the a.equals(b) condition to compare.

The compiler will not do the unboxing for you while you compare, unless you assign the value to a basic type.

淤浪 2024-10-29 02:30:55

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

Please also note that the cache range was -128 to 127 in Java 1.5 but Java 1.6 onward it is the default range i.e. you can set upper value >= 127 by passing -XX:AutoBoxCacheMax=new_limit from command line

罪歌 2024-10-29 02:30:55

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

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

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

It's because you're using the new statement to construct the objetcs.

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

That will print out true. Weird, but Java.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文