覆盖equals方法需要注意的

Effective java读书笔记 第八条

覆盖equals方法其实是非常有讲究的, 许多覆盖方式会导致错误, 并且带来非常严重的后果. 首先看一下文档上对equals方法是怎么说的吧

Object类的equals方法

自反性, 对称性, 传递性, 有木有想起离散数学=-=

首先看一下哪些情况是不用覆盖equals方法的, 这样的话, 类的每个实例都只与它自己相等.

  • 类的每个实例本质上都是唯一的
    什么意思呢, 就是说这个类表示活动实体, 而不是值, 就不需要去覆盖equals了, Object提供的实现就已经足够了. 比如说Thread, GUI的控件等等

  • 不关心类是否提供逻辑相等的测试功能
    比如说java.util.Random覆盖了equals, 用来检查两个Random实例产生的随机数序列是否相同, 其实这并没有什么卵用啊, Object对于equals的实现就够啦

  • 超类已经覆盖了equals, 对于子类也适用*
    这种情况无需再去实现equals

如果类具有自己特有的逻辑相等概念, 而超类的的equals并不是期望的实现的话, 就需要我们去覆盖equals了.

需要覆盖equals的时候就需要遵守上面截图的规范啦:

  • 自反性
    自反性说明一个对象是等于其自身, 自己和自己相等, Object的也就实现了这个了:
publicbooleanequals(Objectobj){
return(this==obj);
}

我就是我, 是颜色不一样的烟火

  • 对称性
    简单来说, 就是a=b成立的话, 那么b=a必定成立.
    来看个例子, 有一个CaseInsensitiveString类, 这个类比较的时候会忽略大小写:
final class CaseInsensitiveString{
    private final String s;
    public CaseInsensitiveString(String s) throws NullPointerException{
        if (s == null) {
            throw new NullPointerException();
        }
        this.s = s;
    }

    @Override
    public boolean equals(Object o) {
        if(o instanceof CaseInsensitiveString) {
            return s.equalsIgnoreCase(((CaseInsensitiveString) o).s);
        } 
        if(o instanceof String) {
            return s.equalsIgnoreCase((String) o);
        }
        return false;
    }
}

覆盖了equals, 判断s与CaseInsensitiveString类的s或者和String忽略大小后是否相等, 否则就返回false, 然后我们来测试一下,

CaseInsensitiveString cis1 = new CaseInsensitiveString("boom!");
        CaseInsensitiveString cis2 = new CaseInsensitiveString("Boom!");
        String s = "BoOM!";
        System.out.println(cis1.equals(cis2));
        System.out.println(cis1.equals(s));
    }

输出正如我们所料, 都是true, 但是别忘了自反性啊, cis1.equals(s)输出true, s.equals(cis1)也应该是输出true的, 事实上s.equals(cis1)的结果是false. 显然违反了对称性, String类的equals并不知道不区分大小写的CaseInsensitiveString类, 因此s.equals(cis1)返回了false.
为了解决这个问题, 只要将企图与String互操作的那段代码从equals中删除就行了

@Override
    public boolean equals(Object o) {
        return o instanceof CaseInsensitiveString && ((CaseInsensitiveString) o).s.equalsIgnoreCase(s);
    }
  • 传递性
    最复杂的就是传递性了, 离散中最麻烦的也是求传递闭包了.
    传递性的意思也很简单的, 就是a=b, b=c的话, 那么a和c也是相等的.

    有一个Point类, 用来表示坐标点
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 x == p.x && y == p.y;
    }
}

然后又有一个ColorPoint类, 来表示带颜色的点

class ColorPoint extends Point {
    private final Color color;
    public ColorPoint(int x, int y, Color color) {
        super(x, y);
        this.color = color;
    }
}

没有复写equals的情况下, 虽然ColorPoint和Point作比较的时候能饭后正确的结果, 但是两个ColorPoint之间做比较的时候忽略了颜色信息, 这显然不是我们想要的结果, 于是乎:

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

这样对了吗? 抱歉, 问题还是很大, 虽然实现了两个有色点之间的比较, 但是当普通点和有色点比较的时候, 违反了对称性, 普通点和有色点比较会忽略颜色, 而有色点和普通点则总是返回false.
继续改:

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

如果o不是ColorPoint, 就用o去比较this, 这样就会忽略颜色信息了, 测试一下:

        ColorPoint p1 = new ColorPoint(1, 1, Color.RED);
        Point p2 = new Point(1, 1);
        ColorPoint p3 = new ColorPoint(1, 1, Color.GREEN);
        System.out.println(p1.equals(p2));
        System.out.println(p2.equals(p3));

返回的都是true, 很好, 对称性的问题解决了, 等等, 这里不是在讨论传递性吗!!!按照传递性来说, p1=p2, p2=p3, 所以p1和p3肯定是相等啊喂, 但是这里很明显就是不相等的, 大家又不是色盲.
这样写呢

@Override
    public boolean equals(Object o) {
        if (o == null || o.getClass() != getClass()) {
            return false;
        }
        Point p = (Point)o;
        return x == p.x && y == p.y;
    }

只有当对象相同时才 比较, 这样虽然解决了问题, 但是却不是我们想要的解决方法, 来看一个更好的实现, 用复合代替继承:

class ColorPoint {
    private final Color color;
    private final Point point;
    public ColorPoint(int x, int y, Color color) {
        if (color == null) {
            throw new NullPointerException();
        }
        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.equals(color);
    }
}
  • 一致性
    如果两个对象是相等的, 就应该保持一直是相等的, 除非这两个对象中有一个或者两个都被修改了, 所以记住: 相等的对象永远相等, 不相等的对象永远不相等.

  • 非空性
    所有的对象都不能为null, 尽管很难想象什么情况下o.equals(null)会返回true. 但是意外抛出NullPointerException异常的可能却不难想象, 所以可以这样写来不允许抛出NullPointerException异常

@Override
public boolean equals(Object o) {
    if (o == null) {
        return false;
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容

  • 背景 一年多以前我在知乎上答了有关LeetCode的问题, 分享了一些自己做题目的经验。 张土汪:刷leetcod...
    土汪阅读 12,723评论 0 33
  • 文章作者:Tyan博客:noahsnail.com | CSDN | 简书 CHAPTER3 Method...
    SnailTyan阅读 732评论 1 4
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,596评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,573评论 18 399
  • (一) 糖炒栗子味诱人, 买上几斤过嘴瘾。 想要开怀吃个够, 那就动手在家炒。 先淘洗,再控水, 热锅倒进加锅盖。...
    E键倾心阅读 804评论 0 1