第八条 覆盖equals请遵守通用的约定(二)

本文探讨了在Java中实现equals方法的最佳实践,特别是在涉及继承时如何确保对称性和传递性。通过复合模式解决复杂对象间的比较问题,并提供了提高equals方法效率的小技巧。

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

传递性,相同类型的传递性出错基本不可能,出错的一般都是写子类扩展属性的情况。

例如两个类
public class Point {
    private int x;
    private int y;

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

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

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

}

public class ColorPoint extends Point {

    private int color;

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

}

如果不提供equals方法,直接使用父类的方法,那么比较时,颜色的值会被忽略掉,假若自己添加一个有颜色的比较,
  @Override
    public boolean equals(Object o) {
        if(!(o instanceof ColorPoint)){
            return false;
        }
        return super.equals(o) && ((ColorPoint)o).color == color;
        
    }
这样做的效果和前一章 刚开始的String和CaseString有异曲同工之妙的错误。
那就是
Point p1 = new Point(1,2);
ColorPoint p2 = new ColorPoint(1,2,Color.RED);
p1.equals(p2)值为true,p2.equals(p1)的值为false,对称性失效。
此时,如果用instanceof对类型进一步判断,确定是Point 或者ColorPoint类型,分开判断,则可以解决这个问题。由于Point是父类,所以如果不属于父类,那么也肯定不是子类型。

 @Override
    public boolean equals(Object o) {

        if(!(o instanceof Point)){
            return false;
        }
        if(!(o instanceof ColorPoint)){
            return o.equals(this);
        }
        return super.equals(o) && ((ColorPoint)o).color == color;
        
    }
如此,解决了对称性的问题,但却又引出另外一个问题,那就是传递性。
ColorPoint p3 = new ColorPoint(1,2, Color.BLUE);
 p1.equals(p2)  p1.equals(p3)都为true,此时 p3.equals(p2)却是false。
一个解决方法是根据类型判断,如果是非类型,则为false,在Point里面equals方法中加入getClass()方法代替instanceof关键字。但又会引起另外一个问题,java讲究的是扩展,继承,此时子类一

些操作就没法用了,就像effective中举的例子。
那怎么办?用哪种方法,此时可以使用复合模式。
public class ColorPoint {

    private int color;
    private Point point;

    public ColorPoint(int x, int y,int color) {
        point = new Point(x,y);
        this.color = color;
    }

    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 == color;

    }
}
不使用继承方法,用复合,相当于在Point的外面包裹了一层,这样就解决了一些难题。复合的好处就是可以在ColorPoint 中直接通过asPoint()返回一个Point进行数据的比较和使用。
大家在使用对象前,一般都会做非空判断,此时可以用instanceof关键字代替,它在进行对象格式校验时,已经做了类型判断。

equals方法,可以使用一些小窍门来提高效率。
1、使用 == 来做比较,这个直接比较的就是地址值,速度最快。
2、使用instanceof 作类型判断,如果不是同一个类型,那就不用继续比较了。
3、参数转换为正确的类型。
4、对每个属性进行检查,对于float 和 double要注意,int类型可以直接用==,String类型用equals,对象的话就递归调用equals吧。正确的使用 && 和 || ,少用三元表达式。

最后书中也提出了三个告诫:
1、覆盖equals时也要覆盖hashCode方法,下一篇介绍。
2、不要使equals太过智能,这个能解决大部分问题就行。
3、重写equals方法,equals(Object o)里面的参数Object类型一定不要写错,写错的话就变成重载了,编译正常,运行数据却出错了,半天也不知道哪里出错。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值