美文网首页
Effective Java - 覆盖equals方法

Effective Java - 覆盖equals方法

作者: DZQANN | 来源:发表于2022-05-10 22:57 被阅读0次

本章主要讨论的是如何规范的覆盖Object的非final方法,以及如何实现Comparable接口

第10条 覆盖equals时请遵守通用约定

不需要覆盖equals的情形

当类不覆盖equals方法的时候,则比较的是对象的内存地址,则每一个对象仅与自身相等。以下几种情况不需要不该equals方法

  1. 本身就是每一个实例唯一(Thread)
  2. 不太关心类是否提供了逻辑相等的测试功能(Pattern)
  3. 父类已经覆盖了,并且父类的行为也完全适用于子类
  4. 类本身的访问级别是私有或者包可见。这时候应该将equals的实现调整为抛出AssertionError

覆盖equals的约定

这里提到的几点看起来都是理所当然的,但是在不同的情形中,实现非常容易违反这些约定。尤其是在Collecion的使用中,如果没有完全遵守的话,非常容易出现问题。

自反性:对象必须等于自身

如果违反了这条约定,在一个实例添加进了Collection集合的时候,再调用containes方法会返回false

对称性:任何两个对象关于它们是否相等的结果保持一致

书中举的例子不太常见,是一个自定义的类在和传入的String类进行比较的时候,会发生这样的问题

public final class CaseInsensitiveString {
    private final String s;

    public CaseInsensitiveString(String s) {
        this.s = Objects.requireNonNull(s);
    }

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

这里在判断equals String的时候,大小写不敏感,但是反过来String equals一定会返回false。

传递性: 如果一个对象等于第二个对象, 第二个对象等于第三个对象, 则第一个对象一定等于第三个对象

这一条一般来说不会有问题,但在继承关系中非常容易出问题

这是一个Point类的equals方法

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

Point类加一个子类,添加一个Color属性,并且覆盖equals

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

当子类equals父类的时候,就会是false

对于这种情形,作者也提到了我们没有办法在扩展实例化类的同时,既增加新的值组件,又能保留equals约定

一种比较好的取巧方法是用组合结构替换继承结构

public class ColorPoint {
  private Point point;
  private Color color;
}

去掉了继承结构,这个问题就会迎刃而解

一致性:如果两个对象相等, 它们就必须始终保持相等, 除非它们被修改了

不要使equals方法依赖不可靠的资源。比如java.net.URLequals实现依赖了主机的IP地址,IP地址的变化概率非常高,很容易造成两次equals调用结果不同

非空性

任何实例都不能equalsnull的结果为true。

对于两个对象的比较,jdk官方提供了Objects.equals,比较了空指针的可能性。个人认为在比较的时候应该尽量使用这个官方方法进行比较。

满足约定的实现方式

  1. 使用==操作符检查参数是否为这个对象的引用, 如果是, 则返回true.
  2. 使用instanceof操作符检查参数是否为正确的类型, 如果不是, 则返回false. 注意这个地方不能使用getClass替换instanceof
  3. 把参数转换成正确的类型.
  4. 对于该类中的每个关键域, 检查参数中的域是否与该对象中对应的域相匹配.

目前Intellj提供了不同的jdk版本覆盖hashcodeequals方法的模板,直接使用就会比较标准的覆盖方法

注意点

  1. 覆盖equals必须同时覆盖hashcode
  2. 不要将equals的入参由Object替换为其它内容,因为这样是重载,并不是覆盖重写。应该养成良好的习惯,在覆盖方法的时候加上@Override注解,及时发现这些错误

总结一下,这一节提到的很多特性,在使用作者最后提供的覆盖模板都可以很好的满足。 对于继承的equals覆盖,个人认为应该避免这种情况的出现,或许可以尝试一下序列化,不过感觉风险也很高。

在TMS中,我有几次覆盖equals的经历。在tariff这边有很多需求是需要将standard view的vo,group成pivot view,这时候就需要覆盖vo,在equals方法中判断除了container size type之外其它的字段是否相等

相关文章

网友评论

      本文标题:Effective Java - 覆盖equals方法

      本文链接:https://www.haomeiwen.com/subject/ggwjurtx.html