本文介绍了 Java 中不同类的相等判定规则以及不同类中 equals 方法的效果。
1. Integer
首先我们来看以下这段代码的运行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| package com.yyj;
public class Equivalence { public static void main(String[] args) { testIntEqual(127); testIntEqual(128);
}
private static void testIntEqual(int value) { Integer a1 = value; Integer b1 = value; System.out.printf("[%d] Auto: %b %b\n", value, a1 == b1, a1.equals(b1));
Integer a2 = Integer.valueOf(value); Integer b2 = Integer.valueOf(value); System.out.printf("[%d] valueOf: %b %b\n", value, a2 == b2, a2.equals(b2));
Integer a3 = new Integer(value); Integer b3 = new Integer(value); System.out.printf("[%d] Integer: %b %b\n", value, a3 == b3, a3.equals(b3));
int a4 = value; int b4 = value; System.out.printf("[%d] Int: %b\n", value, a4 == b4); } }
|
对于参数值127,==
操作符只在使用 new
创建对象的方式上判定两个数为 false
,这是因为操作符 ==/!=
比较的是对象的引用,虽然参与比较的两个引用包含的内容相同,但他们指向了内存中的不同对象。注意这种定义方式在 Java9 之后已经被废弃,因为其效率远不如使用 valueOf
方法。
对于参数值128,可以发现所有创建方式创建的两个相同值的 Integer
对象在 ==
操作符的判定下均为 false
,这是因为出于效率的原因,Integer
会通过享元模式来缓存范围在-128~127内的对象,因此多次调用 Integer.valueOf(127)
生成的其实是同一个对象,而在此范围之外的值则不会这样,即每次调用 Integer.valueOf(128)
返回的都是不同的对象。
因此在使用 Integer
的时候如果需要比较值是否相等应该只是用 equals
方法。
2. Double
我们再来看下面这段代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| package com.yyj;
public class Equivalence { public static void main(String[] args) { testDoubleEqual(0, 0); testDoubleEqual(0, Double.MIN_VALUE); testDoubleEqual(Double.MAX_VALUE, Double.MAX_VALUE - Double.MIN_VALUE * 1000000);
}
private static void testDoubleEqual(double a, double b) { System.out.printf("[%e | %e] double: %b\n", a, b, a == b);
Double a1 = a; Double b1 = b; System.out.printf("[%e | %e] Auto: %b %b\n", a1, b1, a1 == b1, a1.equals(b1));
Double a2 = Double.valueOf(a); Double b2 = Double.valueOf(b); System.out.printf("[%e | %e] valueOf: %b %b\n", a2, b2, a2 == b2, a2.equals(b2));
Double a3 = new Double(a); Double b3 = new Double(b); System.out.printf("[%e | %e] Double: %b %b\n", a3, b3, a3 == b3, a3.equals(b3)); } }
|
理论上浮点数的比较应该是很严格的,即两个数值之间即使有极小的不同也应该不相等。
例如0和 Double.MIN_VALUE
相比较并不相等,但是 Double.MAX_VALUE
减去一百万倍的 Double.MIN_VALUE
却仍等于 Double.MIN_VALUE
,这是因为当一个非常大的数值减去一个相对较小的数值时,非常大的数值并不会发生显著变化,这叫做舍入误差,误差的产生原因是因为机器不能存储足够的信息来表示一个大数值的微小变化。
3. 自定义类
现在你是不是以为在比较对象内容是否相等的情况下都一律用 equals
函数即可?但是并没有这么简单。看下面这段代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| package com.yyj;
public class Equivalence { public static void main(String[] args) { testClassEqual(); }
private static void testClassEqual() { A a1 = new A(10); A a2 = new A(10); System.out.println(a1.equals(a2));
B b1 = new B(10); B b2 = new B(10); System.out.println(b1.equals(b2)); } }
class A { int val;
A(int val) { this.val = val; } }
class B { int val;
B(int val) { this.val = val; }
public boolean equals(Object o) { B tempB = (B)o; return this.val == tempB.val; } }
|
我们创建了两个类 A
的对象,且值相等,但是 equals
方法返回的结果为 false
,这是因为 equals
方法的默认行为是比较引用,如果想比较内容必须像类 B
那样重写 equals
方法。
因此实际上其实是大多数标准库会重写 equals
方法来比较对象的内容而不是他们的引用。