【Effective Java 10.3】覆盖 equals 时请遵守通用约定 —— 传递性

1. 传递性要求

equals约定的第三个要求是,如果 x eq y,y eq z,则 x eq z。

我们很容易在无意识的情况下违反这条约定。特别是 “值” 类组件的超类中,子类对超类 equals 行为的扩展导致 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 == this.x && p.y == this.y;
    }
}

例如,对于 Point 类,如果我们要扩展一个 Point 的子类 ColorPoint。如果值。

public class ColorPoint extends Point {
    private final Color color;

    public ColorPoint(int x, int y, Color color) {
        super(x, y);
        this.color = color;
    }
    // 没有覆写 equals 方法, 而是直接从 Point 继承过来,在比较时颜色信息会被忽略掉
}

如果要在 ColorPoint 中覆写 Point 的 equals 方法,让其在比较时不忽略颜色信息。

public class ColorPoint extends Point {
    private final Color color;

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

    @Override
    public boolean equals(Object o) {
        // 如果 o 不是一个 Point 对象
        if (!(o instanceof Point)) {
            return false;
        }

        // 如果 o 是一个 Point 对象但不是一个 ColorPoint 对象
        // 则无视 color 域进行比较
        if (!(o instanceof ColorPoint)) {
            return o.equals(this);
        }

        // o is a ColorPoint; do a full comparison
        return super.equals(o) && ((ColorPoint) o).color.equals(color);
    }
}

进行如上修改后,ColorPoint 会在进行 “混合比较” 时忽略颜色信息。这种做法提供了对称性,但牺牲了传递性。虽然没有一种令人满意的方法可以既扩展不可实例化的类,又增加组件值,但还是有一种权宜之计,即 “组合由于继承”。不再让 ColorPoint 继承 Point,而是在 ColorPoint 中加入一个私有的 Point 域,以及一个公有视图(view)方法,此方法返回一个承担将 ColorPoint 转换为 Point 的任务。

public class ColorPoint {
    private final Color color;
	private final Point point;
    
    public ColorPoint(int x, int y, Color color) {
        super(x, y);
        this.color = color;
    }
    // 返回 ColorPoint 的 Point 视图,承担修改前的 (Point) colorPoint 的任务
    public Point asPoint() {
        return point;
    }
    
    public boolean equals(Object o) {
        if (!(o instanceof ColorPoint)) {
            return false;
        }
        ColorPoint cp = (ColorPoint) o;
        return cp.point.equals(this.point) && cp.color.equals(color);
    }
}

在实际开发中,对于值类型的对象的equals比较,最好只在同类对象之间进行,尽量不要再超类与子类,子类与子类之间进行 equals 操作

2. Jdk 中一些违反传递性原则的例子

Java 平台类库中,有一些扩展了可实例化的类,并添加了新的值组件。例如,java.sql.Timestampjava.util.Date 进行了扩展,并添加了 nanoseconds 域。Timestamp的实现的确违反了对称性与传递性。因此,其有一个免责声明。

posted on 2022-03-29 20:07  Silgm  阅读(74)  评论(0)    收藏  举报

导航