(Effective Java 2/E) 110. Item 8 - Obey the general contract when overriding equals

Obey the general contract when overriding equals

  • 2판 제목 : equals를 재정의할 때는 일반 규약을 따르라
  • 3판 제목 : equals는 일반 규약을 지켜 재정의하라

equals 함수는 언뜻보면 재정의하기 쉬워보이지만 실수할 여지가 많은 함수이다.

무엇보다 함수의 특성상 실수가 발생할 경우 의도치않은 동작을 수행할 확률이 너무 높다.

1. 재정의를 회피할 수 있는 경우

문제를 피하는 가장 간단한 방법은 equals를 재정의하지않는 것이다.

이 경우 모든 객체는 오직 자기 자신하고만 같다고 판정할 것이다.

재정의를 회피해도 되는 조건은 아래 중 하나만 만족하면 된다.

1. 각각의 객체가 고유하다.

value보다는 entity를 표현하는 Thread와 같은 클래스가 이에 부합한다.

2. 논리적 동일성 검사 방법에 의존하지 않는다

java.util.Random 클래스의 두 객체가 같은 난수를 만드는 지 검사할 필요가 없는 것과 같은 맥락이다.

3. 상위 클래스가 재정의한 equals가 하위 클래스에서 사용하기 충분하다

4. 클래스가 private 혹은 package-private로 선언되었고, equals를 호출할 일이 없다.

이건 논란의 소지가 있는 항목이다.

실수로라도 equals를 호출할 여지가 남아있다면 재정의를 통해 혹시모를 실수를 방어하는 것이 좋다.

2. 재정의가 권장되는 경우

그럼 equals를 재정의하는 것이 권장되는 경우는 어떤 경우일까?

객체의 동일성이 아닌 논리적 동일성의 개념을 지원하는 클래스이거나,

상위 클래스의 equals가 하위 클래스의 니즈를 충족하지 못하는 경우엔 재정의를 해야한다.

대부분의 값 클래스(value class) 가 이에 해당한다.

3. equals 재정의 규약

equals를 재정의한다는 것은 동치 관계(equivalence relation) 를 구현한다는 것이며 아래와 같은 규약을 만족해야 한다.

1. 반사성(reflexivity)
null이 아닌 모든 참조 x가 있을 때, x.equals(x)true를 반환한다.

2. 대치성(symmetry)
null이 아닌 모든 참조 x와 y가 있을 때, x.equals(y)y.equals(x)true일 때만 true를 반환한다.

3. 추이성(transitivity)
null이 아닌 모든 참조 x, y, z가 있을 때, x.equals(y)true이고 y.equals(z)true이면, x.eqauls(z)true이다.

4. 일관성(consistency)
null이 아닌 모든 참조 x, y가 있을 때, equals를 통해 비교되는 정보에 아무런 변화가 없다면, x.equals(y)의 호출 결과는 호출 횟수에 상관없이 항상 같아야 한다.

5. null
null이 아닌 모든 참조 x에 대해, x.equals(null)false이다.

만약 위 규약들을 지키지않으면 개발자는 객체의 비교시 이후 동작을 예측할 수 없다.

4. equals를 구현하기 위해 따라야할 지침

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