8、覆盖equas时请遵守约定

时间:2021-02-19 16:10:16

覆盖equas时请遵守约定

不覆盖的情况

类的每个实例本质上是唯一的(Object本身的equals就能产生正确的行为)

不关心逻辑是否相等

基类已经覆盖了equals,并且从基类继承下来是合适的

类是私有的或是包级私有的,可以确定它的equals方法永远不会调用(这是用可以在它的equals方法中抛出异常,防止调用)

什么时候覆盖

如果类具有自己的逻辑相等的概念的时候,并且基类还没有实现。这也叫“值类”

有一种值类不需要覆盖equals方法,即单例模式的情况下,逻辑相同和对象等同是同一回事,所以不需要覆盖equals方法

equals等价关系的性质

自反性

对称性

比如下面一个不区分大小写的类,这个类的对象调用equals,并传入普通的String类,和普通的String类调用equals并传入这个类的结果是不同的

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));
}
}

传递性

考虑一个Point基类:

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;
}
}

它的子类ColorPoint:

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!
@Override
public boolean equals(Object o) {
if (!(o instanceof ColorPoint))
return false;
//增加了判断颜色,如果不是该类,就返回false,如果是,强转后判断
return super.equals(o) && ((ColorPoint) o).color == color;
}
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);
//结果是true、false, 违反了对称性
System.out.println(p.equals(cp) + " " + cp.equals(p));
}
}

可以改成:

     Broken - violates transitivity!
@Override public boolean equals(Object o) {
//过滤掉不是Point或者其子类的类
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;
}

但是这样会出现传递性的问题

// 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);
//true, true, false
//违反了传递性
System.out.printf("%s %s %s%n", p1.equals(p2), p2.equals(p3),
p1.equals(p3));

所以问题怎么解决:

实际上,我们无法扩展可实例化的类的同时,既增加新的值组件,同时又保留equals约定

当然你也可以这样做:

     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;
}

在基类中用getclass代替instanceof。这样必须是相同的实现类才可以判断。但是继承性被忽略了(因为你继承的时候不一定是添加了新的值组件)

一个权宜之计: 采用组合的形式

//color的枚举
public enum Color {
RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET
}

point类和之前的一样

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;
}
}
//采用组合形式的colorpoint类
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();
}
}

为什么采用组合的形式可以:

因为没有了继承,instanceof 的判断就很精确

注意:

抽象类的子类中增加新的值组件,而不会违反equals的约定

一致性

无论类是否不可变的,都不要依赖于不可靠的资源

非空性

显然对象是空的,equals都调用不了

通常我们在写equals方法的时候,也会加入是否为空引用的判断,但是这样写是不必要的,因为instanceof本身就会对空引用的判断返回false

写equals的实际步骤

判断是否为同一个引用(使用“==”判断)

使用instanceof判断类型

把参数转换成正确的类型

执行判断逻辑

注意事项:

覆盖equals的时候要覆盖hashcode

不要让equals过于只能

不要将equals的参数Object换成其他的