为什么 Java 中不缓存整数?
我知道关于该主题有类似的帖子,但他们并没有完全解决我的问题。当你这样做时:
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(15)
应该非常清楚的是,缓存会对性能产生不可接受的影响——每次创建 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)
在内部完成的。如果您认为自己理解并想测试一下,请预测以下程序的输出。 :
为了额外加分,还可以预测如果所有
==
更改为.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 thatnew Integer(5) == new Integer(5)
evaluates tofalse
.The more interesting question is why
new Object();
should be required to create a unique instance every time? i. e. why isnew Object();
not allowed to cache? The answer is thewait(...)
andnotify(...)
calls. Cachingnew 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 uniqueInteger
objects each containing the value 5 (becauseInteger
extendsObject
).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). Thereforenew Integer(5) == 5
is converted to:new Integer(5).intValue() == 5
(and notnew Integer(5) == new Integer(5)
.One last thing to understand is that autoboxing of
n
is not done bynew Integer(n)
. It is done internally by a call toInteger.valueOf(n)
.If you think you understand and want to test yourself, predict the output of the following program:
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"
当每个人都正确地假设两个新创建的实例是不同的实例时,这可能会破坏在此设计更改之前编写的代码。对于自动装箱来说可以这样做,因为以前不存在自动装箱,但是改变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.
如果您检查源代码,您会看到:
Source: link
这是
==
使用整数返回 boolean true 的性能原因 - 这完全是一个 hack。如果您想比较值,那么您可以使用compareto
或equals
方法。在其他语言中,比如也可以使用
==
来比较字符串,基本道理是一样的,被称为java语言最大的硬伤之一。int
是一种原始类型,由语言预定义并由保留关键字命名。作为原语,它不包含类或任何类关联信息。Integer
是一个不可变的原始类,它通过包私有的本机机制加载并转换为 Class - 这提供了自动装箱并在 JDK1.5 中引入。之前的 JDK1.5int
和Integer
是两个非常不同的东西。If you check the source you see:
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 havecompareto
orequals
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.5int
andInteger
where 2 very different things.在 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.据我了解,无论如何,
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 callnew
, which instantiates a new object, then the constructor gets called. There is no place for the JVM to intervene and turn thenew
into a "grab a cached Integer object based on the value passed into the constructor".Btw, have you considered
Integer.valueOf
? That works.因为那会很糟糕!
首先,这段代码会抛出一个
OutOfMemoryError
:大多数 Integer 对象可能都是短暂的。
其次,如何维护这样一组规范的 Integer 对象?带有某种表格或地图。您将如何仲裁对该地图的访问?通过某种锁定。因此,突然之间,自动装箱将成为线程代码的性能杀手同步噩梦。
Because it would be awful!
First, this code would throw an
OutOfMemoryError
: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.
您的第一个示例是规范的副产品,要求在 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 likeString
? 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.顺便说一句,如果你
这样做,这可能是真的。
这是因为由于您没有使用 new Integer(),因此 JVM(而不是类代码)可以在认为合适的情况下缓存自己的 Integer 副本。现在你不应该基于此编写代码,但是当你说 new Integer(234345) 时,规范保证你肯定会拥有不同的对象。
BTW, If you do
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.
new
表示新
。new Object()
并不是无意义的。new
meansnew
.new Object()
isn't frivolous.新实例就是新实例,所以它们在值上是相等的,但作为对象却不相等。
所以
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 returntrue
.If they were 1 object, as you ask for:
a+=2;
would add 2 to allint = 10
- that would be awful.让我通过提供 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++, wherenew
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.
假设您准确地描述了代码的行为,听起来自动装箱不适用于“获取”(=)运算符,而是听起来像 Integer x = 10;给出对象 xa 内存指针“10”,而不是值 10。因此 ((a == b) == true)( 将计算为 true,因为对象上的 == 对您分配给 10 的内存地址进行操作。
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.
What oracle has to say on the subject.
Notice that the documentation doesn't supply any examples with the '=' operator.
对于
Integer
对象,使用a.equals(b)
条件进行比较。在您比较时,编译器不会为您拆箱,除非您将值分配给基本类型。
For
Integer
objects use thea.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.
另请注意,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
这是因为您使用
new
语句来构造对象。这将打印出
true
。很奇怪,但是Java。It's because you're using the
new
statement to construct the objetcs.That will print out
true
. Weird, but Java.