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를 구현하기 위해 따라야할 지침
==
연산자를 사용하여equals
의 인자가 자기 자신인지 검사하라instanceof
연산자를 사용하여 인자의 자료형이 정확한지 검사하라equals
의 인자를 정확한 자료형으로 변환하라- 중요 필드 각각이 인자로 주어진 객체의 해당 필드와 일치하는지 검사하라
- 구현이 끝났다면 대칭성, 추이성, 일관성의 세 속성을 만족하는 지 검토하라