代码改变世界

重载equals方法时要遵守的通用约定--自反性,对称性,传递性,一致性,非空性

2017-03-12 21:25  ttylinux  阅读(5329)  评论(0编辑  收藏  举报
本文涉及到的概念
1.为什么重载equals方法时,要遵守通用约定
2.重载equals方法时,要遵守哪些通用约定
 
为什么重载equals方法时,要遵守通用约定
Object类的非final方法都有明确的通用约定,这些方法是被设计成被重载的。重载时,如果不遵守通用约定,那么,其它依赖于这些通用约定的类(例如HashMap和HashSet)就无法结合该类一起正常工作----<<effective java>>
 
 
quals方法实现了等价关系,重载时要遵守的通用约定:
a.自反性(reflexive)  对于任何非null的引用值x, x.equals(x)必须返回true。
b.对称性(symmetric)  对于任何非null的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true
c.传递性(transitive)   对于任何非null的引用值x,y和z,如果x.equals(y)返回true,并且y.equals(z)返回true,那么x.equals(z)返回true
d.一致性      对于任何非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)就会一致地返回
true,或者一致地返回false
e.对于任何非null的引用值x,x.equals(null)必须返回false
 
a.自反性
基本上不会违背这一条规定。如果违背了的话,将一个引用添加到一个集合中,然后,调用集合的contains(x)方法,它会返回false。x.equals(x)不等于true,导致contains(x)方法返回false。
 
b.对称性
对于任何非null的引用值x和y, x.equals(y)返回true, y.equals(x)也要返回true
public final class CaseInsensitiveString {
private final String s;

public CaseInsensitiveString(String s) {
if (s == null)
throw new NullPointerException();
this.s = s;
}

// Broken - violates symmetry!
@Override
public boolean equals(Object o) {
if (o instanceof CaseInsensitiveString)
return s.equalsIgnoreCase(((CaseInsensitiveString) o).s);
if (o instanceof String) // One-way interoperability!
return s.equalsIgnoreCase((String) o);
return false;
}

// This version is correct.
// @Override public boolean equals(Object o) {
// return o instanceof CaseInsensitiveString &&
// ((CaseInsensitiveString) o).s.equalsIgnoreCase(s);
// }

public static void main(String[] args) {
CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
String s = "polish";
System.out.println(cis.equals(s) + " " + s.equals(cis));
}
}
可以把上述的例子代码,代入对称性公式,CaseInsensitivesString为x, String为y, CaseInsensitivesString为y.
x.equals(y),y.equals(x)都为true,当y是CaseInsensitivesString类型时;当y为String类型时,y.equals(x),就为false。
 
String类的equals方法的实现: stringInstance.equals(CanseInsensitivesStringIns),它会返回false,因为x不是String类型
public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = count;
        if (n == anotherString.count) {
        char v1[] = value;
        char v2[] = anotherString.value;
        int i = offset;
        int j = anotherString.offset;
        while (n-- != 0) {
            if (v1[i++] != v2[j++])
            return false;
        }
        return true;
        }
    }
    return false;
    }
c.传递性
equals预定的第三个要求是,如果一个对象等于第二个对象,并且第二个对象又等于第三个对象,则第一个对象一定等于第三个对象。
----<<effective java>>
重写类的equals方法后,使用x.equals(y), y.equals(z),x.equals(z),去检验。如果发现不符合,则违反了该通用约定,那么,就要重新实现。
 
下面的例子,违反了传递性:
父类Point,Point类之间的比较,重写equals方法,比较内容是否相等,不是比较引用地址是否相等。
public class Point {
private final int x;
private final int y;

public Point(int x, int y) {
this.x = x;
this.y = y;
}

@Override
public boolean equals(Object o) {
if (!(o instanceof Point))
return false;
Point p = (Point) o;
return p.x == x && p.y == y;
}

// Broken - violates Liskov substitution principle - Pages 39-40
// @Override public boolean equals(Object o) {
// if (o == null || o.getClass() != getClass())
// return false;
// Point p = (Point) o;
// return p.x == x && p.y == y;
// }

// See Item 9
@Override
public int hashCode() {
return 31 * x + y;
}
}

创建一个子类,继承Point类

import java.awt.Color;

public class ColorPoint extends Point {
private final Color color;

public ColorPoint(int x, int y, Color color) {
super(x, y);
this.color = color;
}

// Broken - violates symmetry!
//x为Point,y为ColorPoint
// x.equals(y)为true,但是y.equals(x)为false
@Override
public boolean equals(Object o) {
if (!(o instanceof ColorPoint))
return false;
return super.equals(o) && ((ColorPoint) o).color == color;
}

// Broken - violates transitivity!
// @Override public boolean equals(Object o) {
// if (!(o instanceof Point))
// return false;
//
// // If o is a normal Point, do a color-blind comparison
// if (!(o instanceof ColorPoint))
// return o.equals(this);
//
// // o is a ColorPoint; do a full comparison
// return super.equals(o) && ((ColorPoint)o).color == color;
// }
//p1.euqlas(p2),p2.equals(p3),p1.equals(p3)
//输出结果:输出结果 true, true ,false

public static void main(String[] args) {
// First equals function violates symmetry
Point p = new Point(1, 2);
ColorPoint cp = new ColorPoint(1, 2, Color.RED);
System.out.println(p.equals(cp) + " " + cp.equals(p));

// Second equals function violates transitivity
ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
Point p2 = new Point(1, 2);
ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
System.out.printf("%s %s %s%n", p1.equals(p2), p2.equals(p3), p1.equals(p3));
}
}
p1和p2的比较,不考虑颜色;p2和p3的比较,也不考虑颜色;p1和p3的比较考虑颜色,于是p1和p3不相等。违反了传递性原则,x.equals(y),y.equals(z).x.equals(z)
 
如何解决这个问题?
事实上,这是面向对象语言中关于等价关系的一个基本问题。我们无法在扩展可实例化的类的同时,既增加新的值组件,同时又保留equals约定,除非愿意放弃面向对象的抽象所带来的优势。
也就是,我们无法在扩展父类,然后,同时在子类中保留父类和子类的equals约定。
 
解决办法:
a.复合优先于继承
b.父类是抽象类(没有任何值组件),子类添加值域
 
使用复合的方式来解决
 
// Simple immutable two-dimensional integer point class - Page 37

import java.util.*;

public class Point {
  private final int x;
  private final int y;

  public Point(int x, int y) {
  this.x = x;
  this.y = y;
  }

  @Override public boolean equals(Object o) {
  if (!(o instanceof Point))
  return false;
  Point p = (Point)o;
  return p.x == x && p.y == y;
  }

  // See Item 9
  @Override public int hashCode() {
  return 31 * x + y;
  }
}
public enum Color { RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET }

// Adds a value component without violating the equals contract - Page 40
public class ColorPoint {
  private final Point point;
  private final Color color;

  public ColorPoint(int x, int y, Color color) {
  if (color == null)
  throw new NullPointerException();
  point = new Point(x, y);
  this.color = color;
  }

  /**
  * Returns the point-view of this color point.
  */
  public Point asPoint() {
  return point;
  }

  @Override public boolean equals(Object o) {
  if (!(o instanceof ColorPoint))
  return false;
  ColorPoint cp = (ColorPoint) o;
  return cp.point.equals(point) && cp.color.equals(color);
  }

  @Override public int hashCode() {
  return point.hashCode() * 33 + color.hashCode();
  }
}

/////////////////////////////////////////////////////////////////////////
public class Test {

public static void main(String[] args) {
// First equals function violates symmetry
Point p = new Point(1, 2);
ColorPoint cp = new ColorPoint(1, 2, Color.RED);
System.out.println(p.equals(cp) + " " + cp.equals(p));

// Second equals function violates transitivity
ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
Point p2 = new Point(1, 2);
ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
System.out.printf("%s %s %s%n", p1.equals(p2), p2.equals(p3), p1.equals(p3));
}
}
输出结果:
false false
false false false