JDK的java.lang.Object类中实现了equals函数,其定义说明如下:
其具体的实现代码如下所示:
Java代码
1. public boolean equals(Object obj) {
2. return (this == obj);
3. }
从上面的代码中可以看出,Object类的equals实现只是简单地调用了“==”运算符,也即定义中说明的,对于两个非空对象X和Y,有且仅当X和Y指向同一个对象时该函数才返回true。由于Object类是java中所有类的超类,因而其它任何类都默认继承了此函数。在大多数情况下,此默认的实现方式是合适的,因为任一个类本质上都是唯一的;但是对于那些表示“值”的类(如Integer、String等),它们引入了自己特有的“逻辑相等”的概念,此时就必须修改equals函数的实现。
例如java.lang.String类的实现如下:
Java代码
1. public boolean equals(Object anObject) {
2. if (this == anObject) {
3. return true;
4. }
5. if (anObject instanceof String) {
6. String anotherString = (String)anObject;
7. int n = count;
8. if (n == anotherString.count) {
9. char v1[] = value;
10. char v2[] = anotherString.value;
11. int i = offset;
12. int j = anotherString.offset;
13. while (n-- != 0) {
14. if (v1[i++] != v2[j++])
15. return false;
16. }
17. return true;
18. }
19. }
20. return false;
21. }
而java.lang.Integer类的实现如下:
Java代码
1. public boolean equals(Object obj) {
2. if (obj instanceof Integer) {
3. return value == ((Integer)obj).intValue();
4. }
5. return false;
6. }
String类的equals实现使得当两个String对象所表示的字符串的值是一样的时候就能返回true;而Integer类则当两个Integer对象所持有的整数值是一样的时候就能返回true。
因此,当我们我们自己编写的类需要引入“逻辑相等”的概念,且其超类又没有改写equals的实现时,我们就必须实现自己的equals函数。在上面Object类中关于equals函数的说明中,定义了5条改写equals函数所必须遵守的规范。其中第1条自反性(reflexivity)和第5条通常能够自行满足。下面我们重点介绍下第2点对称性(symmetric)、第3点传递性(transitive)和第4点一致性(consistent)。
对称性
即对于任意两个引用值X和Y,X.equals(Y) 和 Y.equals(X) 返回的结果必须是一致的。下面引用《effect java》上的例子来说明了违反这一特性的后果:
Java代码
1. public class CaseInsensitiveString {
2.
3. private String s;
4.
5. public boolean equals(Object obj){
6. if(obj instanceof CaseInsensitiveString)
7. return s.equalsIgnoreCase(((CaseInsensitiveString)obj).s);
8. if(obj instanceof String)
9. return s.equalsIgnoreCase((String)obj);
10. return false;
11. }
12. }
然后我们定义如下的类来进行测试:
Java代码
1. public void test(){
2. new CaseInsensitiveString("Value");
3. "Value";
4. new ArrayList();
5. list.add(cis);
6. System.out.println(cis.equals(s));
7. System.out.println(s.equals(cis));
8. System.out.println(list.contains(s));
9. }
运行test(),第一条语句的输出为true,而后两条为false。因为CaseInsensitiveString类中equals函数对String对象的比较也可能返回true,只要其大小写不敏感的字符串值相等;而在String类中对任何非String对象都返回false,这样CaseInsensitiveString的实现就违反了对称性,从而导致了第三条语句也返回了意想不到的结果false。这是因为ArrayList的contains函数实现是用实参s对list中每一个对象调用equals函数,若有equals函数返回true,则contains返回true;因而在这里contains函数必然返回false。因此,如果违反了对称性,则可能会得到意料之外的结果。解决此问题的一个办法就是在CaseInsensitiveString类的equals函数中对Stirng对象的比较无论值是否一致都返回false。
传递性
即对于任意的引用值X、Y和Z,如果 X.equals(Y) 和 Y.equals(Z) 都返回true,则 X.equals(Z) 也一定返回true。下面举例说明:
Java代码
1. public class Point {
2. private final int x;
3.
4. private final int y;
5.
6. public Point(int x, int y) {
7. this.x = x;
8. this.y = y;
9. }
10.
11. public boolean equals(Object obj) {
12. if (!(obj instanceof Point))
13. return false;
14. Point p = (Point) obj;
15. return p.x == x && p.y == y;
16. }
17. }
18.
19. public class ColorPoint extends Point {
20.
21. private int z;
22.
23. public ColorPoint(int x, int y, int z) {
24. super(x, y);
25. this.z = z;
26. }
27.
28. public boolean equals(Object obj) {
29. if (!(obj instanceof ColorPoint))
30. return false;
31. ColorPoint cp = (ColorPoint) obj;
32. return super.equals(obj) && cp.z == this.z;
33. }
34. }
在上面的代码中定义了类Point及其子类ColorPoint,并分别改写了equals函数。下面运行如下代码来测试:
Java代码
1. ColorPoint cp1 = new ColorPoint(1,2,3);
2. ColorPoint cp2 = new ColorPoint(1,2,4);
3. Point p = new Point(1,2);
4. System.out.println(cp1.equals(p));
5. System.out.println(p.equals(cp2));
6. System.out.println(cp1.equals(cp2));
结果前两条语句输出“true”,而最后一条语句输出为“false”;从而违反了传递性规范。其主要原因是在基类的equals函数中并未对子类加以区分,从而使得基类与子类之间的比较也能返回true。要解决此方法,《effect java》上提供了“复合代替继承”的方法;另外,如果可以将基类和子类视为不等的话,使用 getClass()函数代替 instanceof 运算符进行比较可以很好的解决这一问题。getClass()会返回此对象的运行期类(runtime class),因为基类和子类属于不同的class,getClass()能够将其区分开来。下面是一段示例的代码:
Java代码
1. public boolean equals(Object obj) {
2. if(obj.getClass() != this.getClass())
3. return false;
4. Point p = (Point) obj;
5. return p.x == x && p.y == y;
6.
7. }