26

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

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

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

4

15 回答 15

21

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

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

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

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

这应该可以解释为什么new Integer(5)需要完成 7 次才能创建 7 个唯一Integer对象,每个对象都包含值 5(因为Integerextends 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”

于 2011-03-11T23:30:27.553 回答
7

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

于 2011-03-11T20:26:27.923 回答
5

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

/**
 * 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 的性能原因==——这完全是一个 hack。如果你想比较值,那么你有comparetoorequals方法。

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

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

于 2011-03-11T20:46:45.557 回答
2

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

于 2011-03-11T20:24:38.387 回答
2

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

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

于 2011-03-11T20:25:31.780 回答
2

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

因为那会很糟糕!

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

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

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

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

于 2011-03-11T21:23:26.590 回答
1

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

所以a == b回不去了true

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

于 2011-03-11T20:26:41.993 回答
1

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

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

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

于 2011-03-11T20:33:42.507 回答
1

new意味着new

new Object()不是轻浮的。

于 2011-03-11T21:23:32.873 回答
1

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

Integer a = 234345;
Integer b = 234345;

if (a == b) {}

这可能是真的。

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

于 2011-03-11T22:30:28.677 回答
1

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

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

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

于 2015-12-08T11:40:29.387 回答
0

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

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

于 2011-03-11T20:25:26.777 回答
0

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

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

甲骨文对这个问题有什么要说的。

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

于 2011-03-11T20:38:29.290 回答
0

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

于 2015-05-13T16:51:05.437 回答
-1

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

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

那将打印出来true。奇怪,但Java。

于 2011-03-11T20:27:43.287 回答