EJ2E Item 8. equals를 재정의 할 떄는 일반적인 계약을 따르라.
참조: Effective Java 2nd Edition Item 8. Obey the general contract when overriding equals
equals 메소드 재정의은 간단해 보이지만 잘못 될 여지가 많다.
equals 메소드 재정의가 필요 없는 경우
- 클래스 특성상 각각의 객체가 유일할 때. ex) Thread
- "논리적인 일치" 확인 기능을 제공하는지 관심 없을 때. ex) Random
- 이미 상위 클래스에서 재정의한 equals를 재공하며, 그 로직이 현재 클래스서도 적당할 때. ex) AbstractSet, AbstractList, AbstractMap
- 클래스가 private 또는 package-private인 경우 equals가 절대로 호출되지 않을거라는 확신이 있을 때.
equals 메소드 재정의가 필요한 경우
- logical equality 개념이 있는 클래스
- 보통 value class(예외, 싱글톤, Enum 타입 - Object의 equals가 곧 logical equality)
- ex) Integer, Date
equals를 재정의할 때 따라야 하는 일반적인 계약(JavaSE6 Object 스펙)
- Reflexive: null이 아닌 레퍼런스 값 x에 대해, x.equals(x)는 반드시 true를 반환해야 한다.
- Symmetric: null이 아닌 레퍼런스 값 x와 y에 대해, y.equals(x)가 true를 반환 경우에 한 해서만 x.equals(y)도 true를 반환해야 한다.
- Transitive: null이 아닌 레퍼런스 값 x, y, z에 대해, x.equals(y)가 true고 y.equals(z)가 true면 x.equals(z)도 반드시 true여야 한다.
- Consistent: null이 나닌 레퍼런스 값 x와 y에 대해, x.equals(y)를 몇 번 호출하든지 계속해서 일관적으로 true를 반환하거나 false를 반환해야 한다.
- null이 아닌 레퍼런스 값 x에 대해, x.equals(null)은 반드시 false를 반환한다
규칙을 어기면 다른 객체가 어떻게 동작할지 예측하기 힘들다.
고품질 equals 메소드 레서피
- 같은 객체를 참조하는 레퍼런스가 아닌지 확인. == 사용.
- 정당한 타입인지 확인할 떄는 instanceof 연산자를 사용,
- 적당한 타입으로 캐스팅
- 각각의 필드가 같은지 확인. primitive 타입은 == 사용, 레퍼런스 타입은 equals 사용
- 메소드 작성을 마친 뒤에, symmetric, transitive, comsistent 한지 단위 테스트를 작성한다.
example
@Override public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof PhoneNumber))
return false;
PhoneNumber pn = (PhoneNumber)o;
return pn.lineNumber == lineNumber
&& pn.prefix == prefix
&& pn.areaCode == areaCode;
}
if (o == this)
return true;
if (!(o instanceof PhoneNumber))
return false;
PhoneNumber pn = (PhoneNumber)o;
return pn.lineNumber == lineNumber
&& pn.prefix == prefix
&& pn.areaCode == areaCode;
}