[Effective Java] equals를 재정의할 때는 일반 규약을 따르라

equals를 재정의할 때는 일반 규약을 따르라

equals 메서드는 재정의하기 쉬워 보이지만 실수할 여지가 많고 이로인한 결과는 끔찍하다. 이런 문제는 equals를 재정의 하지않는것도 방법이긴 하지만 이 경우에는 오직 자기 자신하고만 같은객체가 된다. 다음과 같은 경우에는 그래도 된다.

  1. 각각의 객체가 고유하다. 값 대신 활성 개체를 나타내는 thread 같은 클래스가 이 조건에 부합한다. 이런 클래스는 object 의 equals 메서드를 그대로 사용해도 된다.
  2. 클래스에 “논리적 동일성” 검사 방법이 있건 없건 상관없다. 같은 난수열을 만드는지 검사하는 equals 메서드를 재정의 할 수도 있었지만 클라이언트가 그런 기능을 원할거라 생각하지 않아서 만들지 않았다.
  3. 상위 클래스에서 재정의한 equals가 하위 클래스에서 사용하기에도 적당하다. 예를들어 set, map, list 클래스는 상위 클래스의 equals를 그대로 사용한다.
  4. 클래스가 private 또는 package-private로 선언되었고, equals메서드를 호출할 일이 없다. 이런 경우에도 equals를 재정의 하는게 맞다. 실수로도 호출할 수 있기 때문이다.

그렇다면 바람직한 재정의는?

객체 동일성이 아닌 논리적 동일성의 개념을 지원하는 클래스일 때, 그리고 상위 클래스의 equals가 하위 클래스의 필요를 충족하지 못할 때 재정의해야 한다. equals 메서드를 정의할 때 준수해야 하는 일반 규약은 다음과 같다.

  1. 반사성 null이 아닌 참조 x가 있을 때, x.equals(x)는 true를 반환한다.
  2. 대칭성 null이 아닌 참조 x와 y가 있을 때. x.equals(y)는 y.equals(x)가 true일 때만 true를 반환한다.
  3. 추이성 null이 아닌 참조 x, y, z가 있을 때, x.equals(y)가 true이고 y.equals(z)가 ture이면 x.equals(z)도 true이다.
  4. 일관성 null이 아닌 참조 x와 y가 있을 때, equals를 통해 비교되는 정보에 아무 변화가 없다면, x.equals(y) 호출 결과는 호출 횟수에 상관없이 항상 같아야 한다.
  5. null이 아닌 참조 x에 대해서, x.equals(null)은 항상 false이다.

이것을 동치 관계라고 하는대 사소한 것이지만 이것을 지키지 않아서 나오는 손해는 엄청날 것이다. 그래서 이러한 규칙들을 가지고 equals 메서드를 구현하기 위해 따라야 할 지침들은 다음과 같다.

  1. == 연산자를 사용하여 equals의 인자가 자기 자신인지 검사하라.
  2. instanceof 연산자를 사용하여 인자의 자료형이 정확한지 검사하라.
  3. equals의 인자를 정확한 자료형으로 변환하라.
  4. “중요” 필드 각각이 인자로 주어진 객체의 해당 필드와 일치하는지 검사한다.
  5. equals 메서드 구현을 끝냈다면, 대칭성, 추이성, 일관성의 세속이 만족되는지 검토하라.