理解equals方法
Understanding equals method
J。 Bloch 在他的 effective Java 中为 equals 方法的实现提供了几个规则。他们在这里:
• Reflexive: For any non-null reference value x, x.equals(x) must
return true.
• Symmetric: For any non-null reference values x and y,
x.equals(y) must return true if and only if y.equals(x) returns true.
• Transitive: For any non-null reference values x, y, z, if
x.equals(y) returns true and y.equals(z) returns true, then
x.equals(z) must return true.
• Consistent: For any non-null reference
values x and y, multiple invocations of x.equals(y) consistently
return true or consistently return false, provided no information used
in equals comparisons on the objects is modified.
• For any non-null
reference value x, x.equals(null) must return false.
但是在书的后面他提到了所谓的里氏替换原则:
The Liskov substitution principle says that any important property of
a type should also hold for its subtypes, so that any method written
for the type should work equally well on its subtypes
我看不出它与 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;
}
好吧,违反了然后呢?没看懂。
我觉得他是想说一个点的特征就是它的坐标。所以你会认为这是真的:
new Point(0, 0).equals(new CounterPoint(0, 0));
因为这两个点的坐标相同,即使它们的类型不同。但是提议的equals方法会return false 因为两个对象有不同的类.
如果您以集合为例,这是正确的:
new LinkedList().equals(new ArrayList());
这两个列表的类型不同,但它们的内容相同(在本例中它们都是空的),因此被认为是相等的。
在 equals 方法中通常有两种检查类型的方法:
选项 1:instanceof
if (! (obj instanceof ThisClass)){
return false;
}
此选项尊重 Liskov 替换原则。但是你不能在子 classes 中添加与 equals 方法相关的附加属性而不破坏等价关系(自反、对称、传递)的特征。
选项 2:getClass()
if (obj == null || ! this.getClass().equals(obj.getClass())) {
return false;
}
此选项违反里氏替换原则。但是您 可以 在子 class 中添加与 equals 方法相关的其他属性,而不会破坏等价关系(自反、对称、传递)的特征。
Joshua Bloch 在他的书中警告了这一点 "Effective Java"。
Angelika Langer 但是提到了一种 "mixed-tpye" 比较的方法,如果您可以为其他属性定义默认值:
http://www.angelikalanger.com/Articles/JavaSolutions/SecretsOfEquals/Equals-2.html
缺点是 equals 方法变得相当复杂。
// 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;
}
Ok, violates and what then? I don't understand.
因此,如果您有一个子 class,例如 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 方法将为 return true。
J。 Bloch 在他的 effective Java 中为 equals 方法的实现提供了几个规则。他们在这里:
• Reflexive: For any non-null reference value x, x.equals(x) must return true.
• Symmetric: For any non-null reference values x and y, x.equals(y) must return true if and only if y.equals(x) returns true.
• Transitive: For any non-null reference values x, y, z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) must return true.
• Consistent: For any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
• For any non-null reference value x, x.equals(null) must return false.
但是在书的后面他提到了所谓的里氏替换原则:
The Liskov substitution principle says that any important property of a type should also hold for its subtypes, so that any method written for the type should work equally well on its subtypes
我看不出它与 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;
}
好吧,违反了然后呢?没看懂。
我觉得他是想说一个点的特征就是它的坐标。所以你会认为这是真的:
new Point(0, 0).equals(new CounterPoint(0, 0));
因为这两个点的坐标相同,即使它们的类型不同。但是提议的equals方法会return false 因为两个对象有不同的类.
如果您以集合为例,这是正确的:
new LinkedList().equals(new ArrayList());
这两个列表的类型不同,但它们的内容相同(在本例中它们都是空的),因此被认为是相等的。
在 equals 方法中通常有两种检查类型的方法:
选项 1:instanceof
if (! (obj instanceof ThisClass)){
return false;
}
此选项尊重 Liskov 替换原则。但是你不能在子 classes 中添加与 equals 方法相关的附加属性而不破坏等价关系(自反、对称、传递)的特征。
选项 2:getClass()
if (obj == null || ! this.getClass().equals(obj.getClass())) {
return false;
}
此选项违反里氏替换原则。但是您 可以 在子 class 中添加与 equals 方法相关的其他属性,而不会破坏等价关系(自反、对称、传递)的特征。
Joshua Bloch 在他的书中警告了这一点 "Effective Java"。
Angelika Langer 但是提到了一种 "mixed-tpye" 比较的方法,如果您可以为其他属性定义默认值:
http://www.angelikalanger.com/Articles/JavaSolutions/SecretsOfEquals/Equals-2.html
缺点是 equals 方法变得相当复杂。
// 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; }
Ok, violates and what then? I don't understand.
因此,如果您有一个子 class,例如 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 方法将为 return true。