《Effective Java》漫谈:继承带来的equals难题

文章探讨了Java中的equals方法约定,包括自反性、对称性、传递性和一致性,并举例说明继承如何可能破坏这些约定。通过ColorPoint类的扩展,展示了如何在不同策略下维护或牺牲equals的特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 逻辑等价约定

翻阅 Objects 源码,可以看到 Objects 定义了 equals() 方法,用于指示其他对象是否与此对象逻辑相等。equals 方法有如下约定:

  1. 自反性: 对于任何非空引用值 x,x.equals (x)应该返回 true。

  2. 对称性: 对于任何非空引用值 x 和 y,当且仅当 y.equals (x)返回 true 时,x.equals (y)应该返回 true。

  3. 传递性: 对于任何非空引用值 x、 y 和 z,如果 x.equals (y)返回 true,而 y.equals (z)返回 true,那么 x.equals (z)应该返回 true。

  4. 一致性: 对于任何非空引用值 x 和 y,只要 equals 的比较操作在对象中使用的信息没有被修改,对 x.equals (y)的多次调用一致地返回 true 或者一致地返回 false。

  5. 对于任何非空的引用值 x,x.equals (null)应该返回 false。

2. 继承是如何破坏约定的

如何快速编写 equals 方法:

  1. 使用==操作符检查“参数是否为这个对象的引用”
  2. 使用instanceof操作符检查“参数是否为正确的类型”
  3. 把参数转换为正确的类型
  4. 对于该类中的每个“关键”域,检查参数中的域是否与该对象中对应的域相匹配

我们首先编写一个 Point 类:

class Point {
    private final double x;
    private final double y;

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

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null) {
            return false;
        }
        if(!(o instanceof Point point)) {
            return false;
        }
        return Double.compare(this.x, point.x)==0 && Double.compare(this.y, point.y)==0;
    }

    @Override
    public int hashCode() {
        int hashcode = 0;
        hashcode = 31 * hashcode + Double.hashCode(x);
        hashcode = 31 * hashcode + Double.hashCode(y);
        return hashcode;
    }

    @Override
    public String toString() {
        return "Point{" +
                "x=" + x +
                ", y=" + y +
                '}';
    }

}

接下来我们扩展这个类,为一个点增加颜色信息: 

enum Color {
    BLUE, WHITE, RED, ORANGE, YELLOW, GREEN, PURPLE, BLACK
}
class ColorPoint extends Point {
    private final Color color;

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

    @Override
    public String toString() {
        return "ColorPoint{" +
                super.toString() + ", color="
                + color + '}';
    }
}
1)违反对称性 

我们这样覆写 equals 方法:只有当比较对象为有色点且具有同样的位置和颜色时,才返回 true

    @Override
    public boolean equals(Object o) {
        if(o == this) {
            return true;
        }
        if(!(o instanceof ColorPoint p)) {
            return false;
        }
        return super.equals(o) && this.color==p.color;
    }

但是当我们比较有色点和无色点时,发现违反了对称性:

public class Test {
    public static void main(String[] args) {
        Point p1 = new Point(1,2);
        Point p2 = new ColorPoint(1,2,Color.BLUE);
        System.out.println(p1.equals(p2)); //true
        System.out.println(p2.equals(p1)); //false
    }
}
2)牺牲传递性,保全对称性 

我们可以考虑这样修改 ColorPoint 类中的 equals 方法:当与无色点比较时,忽略颜色信息

    @Override
    public boolean equals(Object o) {
        if(o == this) {
            return true;
        }
        if(!(o instanceof Point)) {
            return false;
        }
        //if o is a normal point
        if(!(o instanceof ColorPoint p)) {
            return o.equals(this);
        }
        //if o is a color point
        return super.equals(o) && this.color==p.color;
    }

这种方法确实提供了对称性,却牺牲了传递性:

public class Test {
    public static void main(String[] args) {
        Point p1 = new Point(1,2);
        Point p2 = new ColorPoint(1,2,Color.BLUE);
        Point p3 = new ColorPoint(1,2,Color.BLACK);
        System.out.println(p1.equals(p2)); //true
        System.out.println(p1.equals(p3)); //true
        System.out.println(p2.equals(p3)); //false
    }
}
3)牺牲重要属性,保全约定 

我们可以考虑用 getClass 测试代替 instanceof 测试,这样可以扩展可实例化的类和增加新的值组件,同时保留 equals 约定:

    // normal point
    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || o.getClass() != this.getClass()) {
            return false;
        }
        Point p = (Point) o;
        return Double.compare(this.x, p.x)==0 && Double.compare(this.y, p.y)==0;
    }

    
    // color point
    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || o.getClass() != this.getClass()) {
            return false;
        }
        ColorPoint p = (ColorPoint) o;
        return super.equals(p) && p.color==this.color;
    }

但是一个有色点仍然是一个点,如果采用了这种方法,它就无法完成任务:

public class Test {
    public static void main(String[] args) {
        ArrayList<Point> pointArrayList = new ArrayList<>();
        pointArrayList.add(new ColorPoint(1,2,Color.BLUE));
        Point searchPoint = new Point(1,2);
        System.out.println(pointArrayList.contains(searchPoint));
    }
}
3. 组合替代继承
class Point implements Comparable<Point>{
    private final double x;
    private final double y;
    private static final Comparator<Point> COMPARATOR =
            Comparator.comparingDouble((Point p) -> p.x)
                    .thenComparingDouble(p -> p.y);

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

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null) {
            return false;
        }
        if(!(o instanceof Point p)) {
            return false;
        }
        return Double.compare(this.x, p.x)==0 && Double.compare(this.y, p.y)==0;
    }

    @Override
    public int hashCode() {
        int hashcode = 0;
        hashcode = 31 * hashcode + Double.hashCode(x);
        hashcode = 31 * hashcode + Double.hashCode(y);
        return hashcode;
    }

    @Override
    public String toString() {
        return "Point{" +
                "x=" + x +
                ", y=" + y +
                '}';
    }

    @Override
    public int compareTo(Point p) {
        return COMPARATOR.compare(this, p);
    }
}

enum Color {
    BLUE, WHITE, RED, ORANGE, YELLOW, GREEN, PURPLE, BLACK
}

class ColorPoint implements Comparable<ColorPoint>{
    private final Point point;
    private final Color color;
    public ColorPoint(double x, double y, Color color) {
        point = new Point(x, y);
        this.color = color;
    }

    //return a normal point
    public Point asPoint() {
        return point;
    }

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

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

    @Override
    public String toString() {
        return "ColorPoint{" +
                point + ", color="
                + color + '}';
    }

    @Override
    public int compareTo(ColorPoint p) {
        return this.point.compareTo(p.asPoint());
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值