学家Bloch在他有效的Java中为equals方法的实现提供了几条规则。他们在这里:
•自反:对于任何非空参考值x,x.equals(x)必须 返回true。
•对称:对于任何非空参考值x和y, 当且仅当y.equals(x)返回true时,x.equals(y)必须返回true。
•Transitive:对于任何非空引用值x,y,z,if x.equals(y)返回true,y.equals(z)返回true,然后 x.equals(z)必须返回true。
•一致:对于任何非空引用 值x和y,x.equals(y)的多次调用始终如一 如果没有使用任何信息,则返回true或始终返回false in equals比较对象被修改。
•对于任何非null 引用值x,x.equals(null)必须返回false。
但后来他在书中提到了所谓的利斯科夫替代原则:
Liskov替代原则说任何重要的财产 一个类型也应该为其子类型保留,以便编写任何方法 该类型应该在其子类型上同样有效
我不知道它与equals
合同的关系。在编写equals实现时我们是否真的应该遵守它?
问题是关于实现子类的方法。以下是本书的例子:
private static final Set<Point> unitCircle;
static {
unitCircle = new HashSet<Point>();
unitCircle.add(new Point(1, 0));
unitCircle.add(new Point(0, 1));
unitCircle.add(new Point(-1, 0));
unitCircle.add(new Point(0, -1));
}
public static boolean onUnitCircle(Point p) {
return unitCircle.contains(p);
}
public class CounterPoint extends Point {
private static final AtomicInteger counter = new AtomicInteger();
public CounterPoint(int x, int y) {
super(x, y);
counter.incrementAndGet();
}
public int numberCreated() { return counter.get(); }
}
以及以下实施:
// Broken - violates Liskov substitution principle (page 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;
}
好的,违反了什么呢?我不明白。
答案 0 :(得分:6)
通常有两种方法可以检查equals方法中的类型:
选项1:instanceof
if (! (obj instanceof ThisClass)){
return false;
}
此选项尊重 Liskov替换原则。但是不能在子类中添加与equals方法相关的其他属性,而不会破坏等价关系的特征(自反,对称,传递)。
选项2:getClass()
if (obj == null || ! this.getClass().equals(obj.getClass())) {
return false;
}
此选项违反了 Liskov替换原则。但是可以在子类中添加与equals方法相关的其他属性,而不会破坏等价关系的特征(自反,对称,传递)。
Joshua Bloch在他的书#34; Effective Java&#34;。
中警告过这一点然而Angelika Langer提到了一种方式,混合tpye&#34;比较,如果您可以为其他属性定义默认值:
http://www.angelikalanger.com/Articles/JavaSolutions/SecretsOfEquals/Equals-2.html
缺点是等于方法变得相当复杂。
// Broken - violates Liskov substitution principle (page 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; }
好的,违反了什么呢?我不明白。
因此,如果您有一个子类,如MyPoint(可能会添加其他方法但不添加其他属性/字段),那么
Point p1 = new Point(x, y);
Point p2 = new MyPoint(x, y);
p1.equals(p2) == false
Set<Point> points = new HashSet<>();
points.add(p1);
points.contains(p2) == false;
尽管这两个对象确实代表了相同的观点。
如果您使用选项1(instanceof),则equals方法将返回true。
答案 1 :(得分:1)
我认为他试图说一个点的特征是它的坐标。所以你会期望这是真的:
new Point(0, 0).equals(new CounterPoint(0, 0));
因为这两个点具有相同的坐标,即使它们没有相同的类型。但是建议的equals方法将返回false,因为这两个对象具有不同的类。
如果你想到集合,例如,这是真的:
new LinkedList().equals(new ArrayList());
这两个列表的类型不同,但它们具有相同的内容(在这种情况下它们都是空的),因此被认为是相同的。