为什么不在Java中缓存整数?
我知道这个主题有类似的post ,但它们并没有完全解决我的问题。 当你这样做时:
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的所有实例在内存中都是同一个对象,那会不会更有意义? 换句话说,为什么我们没有“整数实习”,这类似于“字符串实习”?
更好的是,如果表示同一事物的盒装基元的实例( 无论值(和类型)是否为同一个对象) ,它会更有意义吗? 或者至少正确回应==
?
应该非常清楚的是,缓存具有不可接受的性能损失 – 每次创建Integer时都需要额外的if语句和内存查找。 仅此一点就掩盖了任何其他原因以及其他原因令人痛苦。
至于“正确”回答==,OP在他的正确性假设中是错误的。 整数DO通过一般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
扩展了Object
)。
次要的,不太重要的东西:这个不错的方案中的一个问题来自自动装箱和自动装箱function。 没有该function,您无法进行比较,例如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重置上限值> = 127:AutoBoxCacheMax = from command line”
这可能会破坏在此设计更改之前编写的代码,当每个人都认为两个新创建的实例是不同的实例时。 它可以用于自动装箱,因为之前不存在自动装箱,但改变新装置的含义太危险了,并且可能不会带来太大的好处。 短期对象的成本在Java中并不大,甚至可能低于维护长寿命对象缓存的成本。
如果您检查来源,您会看到:
/** * 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); }
来源: 链接
这是性能原因,为什么==
使用整数返回布尔值true - 它完全是一个黑客。 如果您想比较值,那么您可以使用compareto
或equals
方法。
在其他语言中,例如你可以使用==
来比较字符串,它基本上是相同的原因,它被称为java语言最大的不幸之一。
int
是一种基本类型,由语言预定义并由保留关键字命名。 作为原语,它不包含类或任何类关联信息。 Integer
是一个不可变的原始类,它通过一个包私有的本机机制加载并转换为Class - 这提供了自动装箱,并在JDK1.5中引入。 事先JDK1.5 int
和Integer
有两个非常不同的东西。
在Java中,每次调用new
运算符时,都会分配新内存并创建新对象 。 这是标准的语言行为,据我所知,没有办法绕过这种行为。 即使是标准class级也必须遵守这一规则。
我的理解是, new
将创造一个新的对象,无论如何。 这里的操作顺序是你首先调用new
,它实例化一个新对象,然后调用构造函数。 JVM没有地方可以进行干预,并将new
转换为“根据传递给构造函数的值获取缓存的Integer对象”。
顺便问一下,你考虑过Integer.valueOf
吗? 这样可行。
如果一个带有10的Integer的所有实例在内存中都是同一个对象,那会不会更有意义? 换句话说,为什么我们不具有类似于“字符串实习”的“整数实习”?
因为它太可怕了!
首先,此代码将抛出OutOfMemoryError
:
for (int i = 0; i <= Integer.MAX_VALUE; i++) { System.out.printf("%d\n", i); }
大多数Integer对象可能都是短暂的。
其次,你将如何维护这样一组规范的Integer对象? 用某种表格或地图。 你会如何仲裁对该地图的访问? 有某种锁定。 因此,突然自动装箱将成为线程代码的性能损失同步噩梦。
你的第一个例子是规范的副产品,要求在大约0的某个范围内创建flyweights。永远不应该依赖它。
至于为什么Integer
不像String
一样工作? 我会想象避免已经很慢的过程开销。 你可以使用原语的原因是因为它们明显更快并占用更少的内存。
现在更改它可能会破坏现有代码,因为您正在更改==
运算符的function。
顺便说一句,如果你这样做
Integer a = 234345; Integer b = 234345; if (a == b) {}
这可能是真的。
这是因为你没有使用新的Integer(),如果它认为合适,JVM(不是类代码)可以缓存它自己的Integers副本。 现在你不应该基于此编写代码,但是当你说新的Integer(234345)时,你会得到规则的保证,你肯定会有不同的对象。
new
意味着new
。
new Object()
并不轻浮。
新实例是一个新实例,因此它们的值相等,但它们不等于对象。
所以a == b
不能返回true
。
如果它们是1个对象,则要求: a+=2;
将所有 int = 10
加2 – 这将是可怕的。
让我通过链接到JLS的相关部分,稍微扩展一下ChrisJ和EboMike的答案。
new
是Java中的关键字,允许在类实例创建表达式中使用 ( JLS的第15.9节 )。 这与C ++不同,其中new
是运算符并且可以重载。
表达式总是尝试分配内存,并在每次评估时产生一个新对象( 第15.9.4节 )。 所以在那时,缓存查找已经太晚了。
假设你准确地描述了你的代码行为,听起来像autoboxing不能在’gets’(=)操作符上工作,而是听起来像整数x = 10; 给对象xa的内存指针’10’而不是10的值。因此((a == b)== true)(将评估为true,因为== on objects对你分配给10的内存地址进行操作。
那么什么时候应该使用自动装箱和拆箱? 仅在引用类型和基元之间存在“阻抗不匹配”时才使用它们,例如,当您必须将数值放入集合时。 将自动装箱和拆箱用于科学计算或其他对性能敏感的数字代码是不合适的。 Integer不能替代int; autoboxing和unboxing模糊了原始类型和引用类型之间的区别,但它们并没有消除它。
什么神谕在这个问题上有所说。
请注意,文档不提供带有’=’运算符的任何示例。
对于Integer
对象,使用a.equals(b)
条件进行比较。
除非您将值分配给基本类型,否则编译器在您进行比较时不会为您执行拆箱操作。
还请注意,Java 1.5中的缓存范围是-128到127,但是Java 1.6以上是默认范围,即您可以通过从命令行传递-XX来设置上限值> = 127 :AutoBoxCacheMax = new_limit
这是因为您正在使用new
语句来构造objetcs。
Integer a = Integer.valueOf(10); Integer b = Integer.valueOf(10); System.out.println("a == b: " + (a == b));
这将打印出true
。 奇怪,但Java。