本文参考

本篇文章参考自《Effective Java》第三版第十条"Obey the general contract when overriding equals"

the conditions when each instance of the class is equal only to itself

  • Each instance of the class is inherently unique —— 类的每一个实例本就彼此不同,例如Thread类,每一个线程仅和自身相等
  • There is no need for the class to provide a "logical equality" test —— 该类不具备"逻辑相等"的特点,例如两个字符串的相等需要比较二者每一个字符是否相等,因为它具备"逻辑相等"的特点(这些类可以被称为"值类"),而某些类的设计,如Pattern类,则没有提供equals()方法的重载来判断两个正则表达式是否相等,即不需要考虑"逻辑相等的情况"
  • A superclass has already overridden equals, and the superclass behavior is appropriate for this class —— 父类的equals()方法同样适用于子类,所以子类不需要再进行重载,例如绝大多数的Set实现类都继承了AbstractSet的equals()方法,下面是AbstractSet的equals()方法源码,显然已适用于绝大多数的Set实现

public boolean equals(Object o) {
    if (o == this)
        return true;

    if (!(o instanceof Set))
        return false;
    Collection<?> c = (Collection<?>) o;
    if (c.size() != size())
        return false;
    try {
        return containsAll(c);
    } catch (ClassCastException unused) {
        return false;
    } catch (NullPointerException unused) {
        return false;
    }
}

  • The class is private or package-private, and you are certain that its equals method will never be invoked —— 这个类是私有的或是包级私有的,可以确保它的equals()方法不会被调用,若被调用,可以将equals()方法设计为返回异常

value classes

前文已经讲到了"值类"具备"逻辑相等"的特点,但是注意并不是所有的"值类"都需要重载equals()方法,例如在"用静态工厂方法代替构造器"中我们提到了Boolean类的静态构造方法valueOf(booean b),它返回两个固定的类实例new Boolean(true)和new Boolean(false),若能够确保每个值至多只存在一个实例,则不需要重载equals()方法

枚举类型是其中一个特例

One kind of value class that does not require the equals method to be overridden is a class that uses instance control to ensure that at most one object exists with each value

general contract

  • Reflexive: For any non-null reference value x, x.equals(x) must return true
  • Symmetric: For any non-null reference values x and y, x.equals(y) must return true if and only if y.equals(x) returns true
  • Transitive: For any non-null reference values x, y, z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) must return true
  • Consistent: For any non-null reference values x and y, multiple invocations of x.equals(y) must consistently return true or consistently return false, provided no information used in equals comparisons is modified
  • For any non-null reference value x, x.equals(null) must return false

上述分别是自反性、对称性、传递性、一致性和x.equals(null) == false

当我们重载equals()方法时,应该问自己三个问题:它是否是对称的、传递的、一致的

There is no way to extend an instantiable class and add a value component while preserving the equals contract

我们无法在扩展(继承)可实例化的类的同时,既增加新的值组件,同时又保留equals约定,这在Java的Timestamp类中也有体现,Timestamp类继承了java.util.Date类,下面是源码注释

The Timestamp.equals(Object) method never returns true when passed an object that isn't an instance of java.sql.Timestamp, because the nanos component of a date is unknown. As a result, the Timestamp.equals(Object) method is not symmetric with respect to the java.util.Date.equals(Object) method. Also, the hashCode method uses the underlying java.util.Date implementation and therefore does not include nanos in its computation.

原文还指出了一种错误的写法例子,首先是父类Point的声明

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 == x && p.y == y;
  }

  @Override public int hashCode() {

    return 31 * x + y;
  }
}

其次是子类ColorPoint类的声明

public class ColorPoint extends Point {

  private final Color color;

  public ColorPoint(int x, int y, Color color) {

    super(x, y);

    this.color = color;
  }

  // Broken - violates symmetry

  @Override public boolean equals(Object o) {

    if (!(o instanceof ColorPoint))

      return false;

    return super.equals(o) && ((ColorPoint) o).color == color;
  }
}

当ColorPoint类的实例调用equals()方法和Point类的实例进行比较时,总是返回false,显然违背了对称性

但是我们可以通过"单向关联",将原本的"父类"作为成员变量加入到原本的"子类"中,通过非继承的方式,来遵守equals()方法的约定

// Adds a value component without violating the equals contract
public class
ColorPoint {

  private final Point point;

  private final Color color;

  public ColorPoint(int x, int y, Color color) {

    point = new Point(x, y);

    this.color = Objects.requireNonNull(color);
  }

  /**
   * Returns the point-view of this color point.
   */

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

  @Override public int hashCode() {

    return 31 * point.hashCode() + color.hashCode();
  }
}

do not write an equals method that depends on unreliable resources

倘若equals()方法依赖于不可靠的资源,可能会违背"一致性"约定,因为在不同的场合下调用equals()方法时,可能会有不同的结果

例如Java中的URL类,在源码注释中我们可以看到equals方法被标注为不符合"一致性"约定

The defined behavior for equals is known to be inconsistent with virtual hosting in HTTP

因为URL类实例的比较涉及IP地址是否相等,而IP地址可能会在不同网络环境下发生变化,所以是"不可靠"资源,无法满足"一致性"约定

here's a recipe for a high-quality equals method

  • Use the == operator to check if the argument is a reference to this object
  • Use the instanceof operator to check if the argument has the correct type
  • Cast the argument to the correct type
  • For each "significant" field in the class, check if that field of the argument matches the corresponding field of this object
  • Always override hashCode when you override equals(item 11)

上述提到的AbstractSet类的equals()方法就是一个很好的例子

public boolean equals(Object o) {

  // Use the == operator to check if the argument is a reference to this object

  if (o == this)

    return true;

  // Use the instanceof operator to check if the argument has the correct type

  if (!(o instanceof Set))

    return false;

  // Cast the argument to the correct type

  Collection<?> c = (Collection<?>) o;

  // check if that field of the argument matches the corresponding field of this object

  if (c.size() != size())

    return false;

  try {

    return containsAll(c);
  } catch (ClassCastException unused) {

    return false;
  } catch (NullPointerException unused) {

    return false;
  }
}

特别的,对于浮点型数值的比较,Java提供了Float.compare()和Double.compare()方法来考虑Float.NaN、-0.0f和0.0f这样特殊的情况,而它们重载的equals()方法没有对这种情况进行考虑,下面是equals()方法的源码注释

If f1 and f2 both represent Float.NaN, then the equals method returns true, even though Float.NaN==Float.NaN has the value false.
If f1 represents +0.0f while f2 represents -0.0f, or vice versa, the equal test has the value false, even though 0.0f==-0.0f has the value true.

因此,在比较两个值类实例的大小前,还要注意它的特殊取值

Idea自动生成equals()方法

public class TestAutoEquals {

  private String username;

  private int age;

  private boolean male;

  private String password;
}

idea提供了不同的"自动化"实现方式

  • idea default

@Override
public boolean equals(Object o) {

  if (this == o) return true;

  if (o == null || getClass() != o.getClass()) return false;

  TestAutoEquals that = (TestAutoEquals) o;

  if (age != that.age) return false;

  if (male != that.male) return false;

  if (!username.equals(that.username)) return false;

  return password.equals(that.password);
}

用getClass代替instanceof运算符来判断是否是同一个类的实例,尽管这样做能够解决上述"There is no way to extend an instantiable class and add a value component while preserving the equals contract"的问题,但有时候不能够采用这种替换方案,例如继承自同一个接口的不同实现类之间的比较或是父类和子类之间的比较

  • Apache commons-lang / commons-lang3

@Override
public boolean equals(Object o) {

  if (this == o) return true;

  if (o == null || getClass() != o.getClass()) return false;

  TestAutoEquals that = (TestAutoEquals) o;

  return new EqualsBuilder()
         .append(age, that.age)
         .append(male, that.male)
         .append(username, that.username)
         .append(password, that.password)
         .isEquals();
}

由特殊的Builder模式实现对每个成员变量的相等判断

  • Google guava / Java 7+

@Override
public boolean equals(Object o) {

  if (this == o) return true;

  if (o == null || getClass() != o.getClass()) return false;

  TestAutoEquals that = (TestAutoEquals) o;

  return age == that.age &&

         male == that.male &&

         Objects.equal(username, that.username) &&

         Objects.equal(password, that.password);
}

此处的Objects可以来自java.util包,也可以来自com.google.common.base包

另外可以由Google开发的AutoValue框架自动生成equals()方法,并且可以结合builder构建者模式,将在下一篇文章中讲解

Effective Java —— 覆盖equals时遵守通用约定的更多相关文章

  1. 第8条:覆盖equals时遵守通用约定

    如果不需要覆盖equals方法,那么就无需担心覆盖equals方法导致的错误. 什么时候不需要覆盖equals方法? 1.类的每个实例本质上是唯一的. 例如对于Thread,Object提供的equa ...

  2. Effective Java —— 覆盖equals时总要覆盖hashCode

    本文参考 本篇文章参考自<Effective Java>第三版第十一条"Always override hashCode when you override equals&quo ...

  3. Effective Java 第三版——10. 重写equals方法时遵守通用约定

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  4. Item 8 覆盖equals时请遵守通用约定

    在覆盖equals方法的时候,你必须要遵守它的通用约定,不遵守,写出来的方法,会出现逻辑错误.下面是约定的内容:   equals方法实现了等价关系:   自反性.对于任何非null的引用值,x.eq ...

  5. 第8条:覆盖equals时请遵守通用约定

    第8条:覆盖equals时请遵守通用约定 引言:尽管Object是一个具体类,但是设计它主要是为了拓展.它所有的非final方法(equals.hashCode.toString.clone和fina ...

  6. 覆盖equals方法时请遵守通用约定

    覆盖equals方法时请遵守通用约定   覆盖equals方法看起来很简单,但是有许多覆盖方式会导致错误,并且后果很严重.最容易避免这种类问题的方法就是不覆盖equals方法,在这种情况下,类的每个实 ...

  7. Item 9 覆盖equals时总要覆盖hashCode

    为什么覆盖equals时,总要覆盖hashCode?   原因是,根据Object规范: 如果两个对象根据equals(Object)方法比较是相等的,那么调用这两个对象中任意一个对象的hashCod ...

  8. 《Effective Java》笔记45-56:通用程序设计

    将局部变量的作用域最小化,可以增强代码的可读性和可维护性,并降低出错的可能性. 要使用局部变量的作用域最小化,最有力的方法就是在第一次使用它的地方才声明,不要过早的声明. 局部变量的作用域从它被声明的 ...

  9. 第八条:覆盖equals时请遵守通用约定

    ==是物理相等 equals是逻辑相等 因为每个类的实例对象本质上都是唯一的 ,利用物理相等(==)是指一个实例只能相等于它自己. 利用逻辑相等是(equals)指 一个实例是否和另一个实例的某些关键 ...

随机推荐

  1. Linux系统发现占用CPU达100%的进程并处理

    转至:https://blog.csdn.net/xinxin_2011/article/details/84936581 服务器使用的是Centos7.2 64位系统.发现服务器异常,一般先想到用t ...

  2. spring 使用depends-on, lazy-init, defalut-lazy-init

    depends-on 如果一个bean是另一个bean的依赖, 可以使用ref属性或者<ref/>标签来实现依赖 那么被依赖bean一定是要比依赖bean率先实例化, 而depends-o ...

  3. Java递归与基础复习

    Day01-基础复习,递归 1.递归 定义:指在当前方法内调用自己,即函数内部调用本函数 分类: 直接递归和间接递归 直接递归成为方法自身调用自己 间接递归可以A方法调用B方法,B方法调用C方法,C方 ...

  4. 【计算机网络】TCP的三次握手与四次挥手

    今天讲一下TCP的三次握手与四次挥手 1.首先说TCP 协议的特点:TCP是在不可靠的IP层之上实现的可靠的数据传输协议,它主要解决传输的可靠.有序.无丢失和不重复问题.TCP 是TCP/IP 体系中 ...

  5. 《手把手教你》系列基础篇(七十七)-java+ selenium自动化测试-框架设计基础-TestNG依赖测试- 上篇(详解教程)

    1.简介 今天主要是讲解和分享:TestNG中一个类中有多个测试方法的时候,多个测试方法的执行顺序或者依赖关系的问题.如果不用dependsOnMethods,testNG会自动根据@Test方法名称 ...

  6. Python基础练习之购物车

    #前置知识点 # enumerate(LIST) # 输出一个元组,第一个为下标,第二个为元素 # a = [1, 2, 3, 4] # for i in enumerate(a): # print( ...

  7. ArcMap进行天空开阔度(SVF)分析

    这里的SVF并不是生物学或医学的(Stromal Vascular Fraction),而是指GIS中的(Sky View Factor,SVF),即为(城市)天空开阔度. 城市天空开阔度(Sky V ...

  8. ArcGIS对进行数据拓扑修改

    空间数据处理--题目1 土地利用数据每年都在发生变化,故每年都要根据去年的数据进行修改.请根据以下要求,修改A区域的数据并对B区域已做好的数据进行拓扑检查. 一. 数据说明(见"题目1&qu ...

  9. 半吊子菜鸟学Web开发1 --配置开发环境

    先说说我自己的情况,我算是一个半吊子菜鸟,对web开发熟练度为0,但是对熟悉C++和Python 所以这里开始记录我学习Web开发的历程,看看我这里学习的程序,能够学到什么地方. 首先是配置环境,我的 ...

  10. JDBC中大数据量的分页解决方法?

    最好的办法是利用sql语句进行分页,这样每次查询出的结果集中就只包含某页的数据内容. sql语句分页,不同的数据库下的分页方案各不一样,下面是主流的三种数据库的分页sql: oracle: selec ...