为什么这些==但不是`equals()`?

当涉及到intInteger和其他类型的数字时,我对Java处理==equals()的方式感到有点困惑。 例如:

 Integer X = 9000; int x = 9000; Short Y = 9000; short y = 9000; List results = new ArrayList(); // results.add(X == Y); DOES NOT COMPILE 1) results.add(Y == 9000); // 2) results.add(X == y); // 3) results.add(X.equals(x)); // 4) results.add(X.equals(Y)); // 5) results.add(X.equals(y)); // 6) System.out.println(results); 

输出(也许你应该先猜测):

 [true, true, true, false, false] 
  1. X == Y不编译是可以预期的,是不同的对象。
  2. 我有点惊讶Y == 9true ,因为9默认是一个int ,并且假设1)甚至没有编译。 请注意,您不能将int放入期望Short的方法中,但在这里它们是相等的。
  3. 出于与两个相同的原因,这是令人惊讶的,但似乎更糟。
  4. 毫不奇怪,因为x被自动装箱到和Integer
  5. 不足为奇,因为不同类中的对象不应该equal()
  6. 什么?? X == ytrueX.equals(y)false ? 不应该==总是比equals()更严格?

如果有人能帮我理解这一点,我会很感激。 出于什么原因,==和equals()的行为方式如何?

编辑:我已将9更改为9000以显示此行为与-128到127之间的整数表现的任何exception方式无关。

编辑:好的,如果你认为你理解这些东西,你应该考虑以下几点,只是为了确保:

 Integer X = 9000; Integer Z = 9000; short y = 9000; List results = new ArrayList(); results.add(X == Z); // 1) results.add(X == y); // 2) results.add(X.equals(Z)); // 3) results.add(X.equals(y)); // 4) System.out.println(results); 

输出:

 [false, true, true, false] 

我理解的原因是:

  1. 不同的实例,如此不同。
  2. X取消装箱,然后相同的值,如此相等。
  3. 相同的价值,如此平等。
  4. y不能装入Integer所以不能相等。

的原因

 X == y 

真实与二进制数字促销有关。 当等于运算符的至少一个操作数可转换为数字类型时,将使用数字相等运算符 。 首先,第一个操作数是未装箱的。 然后,两个操作数都转换为int

 X.equals(y) 

是一个正常的函数调用。 如上所述, y将被自动装箱为Short对象。 如果参数不是Integer实例,则Integer.equals始终返回false。 通过检查实现可以很容易地看出这一点。

有人可能会说这是一个设计缺陷。

(小)整数实例被缓存,因此不变的x == y被保留用于小实例(实际上-127 +128,取决于JVM):

 Integer a = 10; Integer b = 10; assert(a == b); // ok, same instance reused a = 1024; b = 1024; assert(a == b); // fail, not the same instance.... assert(a.equals(b)); // but same _value_ 

编辑

4)和5)产生假,因为equals检查类型: X是整数,而Y是短。 这是java.lang.Integer #equals方法:

 public boolean equals(Object obj) { if (obj instanceof Integer) { return value == ((Integer)obj).intValue(); } return false; } 

这个故事的士气:

自动装箱/拆箱令人困惑,类型促销也是如此。 它们共同构成了良好的谜语,但却是可怕的代码。

在实践中,使用小于int的数字类型很少有意义,我几乎倾向于配置我的eclipse编译器将所有自动装箱和-unboxing标记为错误。

你的问题不仅仅是它如何对待==而是自动装箱……当你比较Y和9时,你正在比较两个相等的原语,在最后两个案例中你只是因为那是等于工作而得到假。 只有两个对象具有相同的类型且具有相同的值时,它们才相等。 当你在“X.equals(y)”中说你做Integer.equals(Short)并查看Integer.equals()的实现时它会失败:

  public boolean equals(Object obj) { if (obj instanceof Integer) { return value == ((Integer)obj).intValue(); } return false; } 

由于自动装箱,最后两个将导致相同的故障,因为它们都将作为短裤传递。

编辑:忘了一件事……在results.add的情况下(X == y); 它将取消装箱X并执行(X.intValue()== y)恰好是真的以及9 == 9

这种自动转换称为自动装箱。

我记得覆盖“equal(object obj)”的一个好习惯是首先检查传入的参数的类型 。所以perhap这会导致X.equals(Y)false 。 你可以查看源代码来挖掘真相:)

关于autoboxing如何工作以及如何缓存“小”值Integer对象的更多细节:

当一个原始int被自动装入一个Integer时,编译器会通过调用Integer.valueOf(…)来代替它。 那么,以下内容:

 Integer a = 10; 

由编译器替换为以下内容:

 Integer a = Integer.valueOf(10); 

类Integer的valueOf(…)方法维护一个缓存,该缓存包含介于-127和128之间的所有值的Integer对象。如果使用此范围内的值调用valueOf(…),则该方法返回一个缓存中的现有对象。 如果该值超出范围,则返回使用指定值初始化的新Integer对象。 (如果您想确切知道它是如何工作的,请在JDK安装目录中查找文件src.zip ,并在其中查找类java.lang.Integer的源代码。)

现在,如果你这样做:

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

你会看到true是打印的 – 但不是因为a和b具有相同的值,而是因为a和b指的是同一个Integer对象,所以Integer.valueOf(…)返回的缓存中的对象。

如果更改值:

 Integer a = 200; Integer b = 200; System.out.println(a == b); 

然后打印false ,因为200超出了缓存的范围,因此a和b引用两个不同的Integer对象。

不幸的是,==用于值类型的对象相等,例如包装类和Java中的String – 这是违反直觉的。

如果需要,Java会自动将Integer转换为int。 同样适用于Short。 此function称为自动装箱和自动装箱。 你可以在这里阅读它。

这意味着当您运行代码时:

 int a = 5; Integer b = a; System.out.println(a == b); 

Java将其转换为:

 int a = 5; Integer b = new Integer(a); System.out.println(a == b.valueOf());