equals를 재정의할 때는 일반 규약을 따르라
equals 메서드는 재정의하기 쉬워 보이지만 실수할 여지가 많고 이로인한 결과는 끔찍하다. 이런 문제는 equals를 재정의 하지않는것도 방법이긴 하지만 이 경우에는 오직 자기 자신하고만 같은객체가 된다. 다음과 같은 경우에는 그래도 된다.
- 각각의 객체가 고유하다. 값 대신 활성 개체를 나타내는 thread 같은 클래스가 이 조건에 부합한다. 이런 클래스는 object 의 equals 메서드를 그대로 사용해도 된다.
- 클래스에 “논리적 동일성” 검사 방법이 있건 없건 상관없다. 같은 난수열을 만드는지 검사하는 equals 메서드를 재정의 할 수도 있었지만 클라이언트가 그런 기능을 원할거라 생각하지 않아서 만들지 않았다.
- 상위 클래스에서 재정의한 equals가 하위 클래스에서 사용하기에도 적당하다. 예를들어 set, map, list 클래스는 상위 클래스의 equals를 그대로 사용한다.
- 클래스가 private 또는 package-private로 선언되었고, equals메서드를 호출할 일이 없다. 이런 경우에도 equals를 재정의 하는게 맞다. 실수로도 호출할 수 있기 때문이다.
그렇다면 바람직한 재정의는?
객체 동일성이 아닌 논리적 동일성의 개념을 지원하는 클래스일 때, 그리고 상위 클래스의 equals가 하위 클래스의 필요를 충족하지 못할 때 재정의해야 한다. equals 메서드를 정의할 때 준수해야 하는 일반 규약은 다음과 같다.
- 반사성 null이 아닌 참조 x가 있을 때, x.equals(x)는 true를 반환한다.
- 대칭성 null이 아닌 참조 x와 y가 있을 때. x.equals(y)는 y.equals(x)가 true일 때만 true를 반환한다.
- 추이성 null이 아닌 참조 x, y, z가 있을 때, x.equals(y)가 true이고 y.equals(z)가 ture이면 x.equals(z)도 true이다.
- 일관성 null이 아닌 참조 x와 y가 있을 때, equals를 통해 비교되는 정보에 아무 변화가 없다면, x.equals(y) 호출 결과는 호출 횟수에 상관없이 항상 같아야 한다.
- null이 아닌 참조 x에 대해서, x.equals(null)은 항상 false이다.
이것을 동치 관계라고 하는대 사소한 것이지만 이것을 지키지 않아서 나오는 손해는 엄청날 것이다. 그래서 이러한 규칙들을 가지고 equals 메서드를 구현하기 위해 따라야 할 지침들은 다음과 같다.
- == 연산자를 사용하여 equals의 인자가 자기 자신인지 검사하라.
- instanceof 연산자를 사용하여 인자의 자료형이 정확한지 검사하라.
- equals의 인자를 정확한 자료형으로 변환하라.
- “중요” 필드 각각이 인자로 주어진 객체의 해당 필드와 일치하는지 검사한다.
- equals 메서드 구현을 끝냈다면, 대칭성, 추이성, 일관성의 세속이 만족되는지 검토하라.