규칙 8 equals를 재정의할 때는 일반 규약을 따르라
- 각각의 객체가 고유하다
- 클래스에 논리적 동일성(logical equality), 검사 방법이 있건 없건 상관없다.
- 상위 클래스에서 재정의한 equals가 하위 클래스에서 사용히기에도 적당하다.
- 클래스가 private 또는 package-private로 선언되었고, equals 메서드를 호출할 일이 없다.
- 반사성(Reflexivity): 모든 객체는 자기 자신과 같아야 한다는 뜻이다.
- 대칭성(Symmetry): 두 객체에게 서로 같은지 물으면 같은 답이 나와야 한다는 것이다. 첫 번째 요구사항과는 달리, 실수하면 쉽게 깨지는 요구사항이다.
// 대칭성 위반!
public final class CaseInsensitiveString {
private final String s;
public CaseInsensitiveString(String s) {
if (S==nuU)
throw new NullPointerException();
this.s=s;
}
// 대칭성 위반!
@Override public boolean equals(Object o) {
if (o instanceof CaselnsensitiveString)
return s.equalsIgnoreCase(((CaseInsensitiveString) o).s) ;
if (o instanceof String) //한방향으로만정상동작!
return s.equalsIgnoreCase((String) o);
return false;
}
}
- 추이성(Transitivity): 첫 번째 객체가 두 번째 객체와같고, 두 번째 객체가 세 번째 객체와 같다면 첫 번째 객체와 세 번째 객체도 같아야 한다는 것이다.
- 일관성(Consistency): 일단 같다고 판정된 객체들은 추후 변경되지 않는 한 계속 같아야 한다는 것이다.
- 널(Null)에 대한 비 동치성(Non-nullity): 모든 객체는 null과 동치 관계에 있지 아니한다는 요구조건이다.
- equals가 따라야 할 규약을 어기면, 그 객체를 만난 다른 객체들이 어떻게 행동할지 예측할 수 없게 된다.
- 객체 생성가능(instantiable) 클래스를 계승하여 새로운 값 컴포넌트를 추가하면서 equals 규약을 어기지 않을 방법은 없다.
- 신뢰성이 보장되지 않는 자원(unreliable resource)들을 비교히는 equals를 구현하는 것은 삼가라.
- equals 메서드를 구현하기 위해 따라야 할 지침
- == 연산자를 사용하여 equals의 인지가 자기 자신인지 검사하라.
- instanceOf 연산지를 사용하여 인자의 자료형이 정확한지 검사하라.
- equals의 인자를 정확한 자료형으로 변환하라.
- ‘중요’ 필드 각각이 인자로 주어진 객체의 해당 필드와 일치하는지 검사한다.
- equals 메서드 구현을 끝냈다면, 대칭성, 추이성, 일관성의 세 속성이 만족되는지 검토하라.
규칙 9 equals를 재정의할 때는 반드시 hashCode도 재정의하라
- equals 메서드를 재정의하는 클래스는 반드시 hashCode 메서드도 재정의 해야 한다.
- HashMap, HashSet, HashTable 같은 해시(hash) 기반 컬렉션과 함께 시용하면 오동작
- hashCode를 재정의하지 않으면 위반되는 핵심 규약은 두번째다. 같은 객체는 같은 혜시 코드 값을 가져야 한다는 규약이 위반되는 것이다.
- HashMap은 성능 최적화를 위해 내부에 보관된 항목의 해시 코드를 캐시해 두고, 캐시된 해시 코드가 없는 객체는 동일성 검사조차 하지 않음
규칙 10 toString은 항상 재정의하라
- toString을 잘 만들어 놓으면 클래스를 좀 더 쾌적하게 사용할 수 있다.
- 가능하다면 toString 메서드는 객체 내에 중요 정보를 전부 담아 반환해야 한다.
- toString이 반환하는 문자열의 형식을 명시하건 그렇지 않건 간에, 어떤 의도인지는 문서에 분명하게 남겨야 한다.
- toString이 반환하는 문자열에 포함되는 정보들은 전부 프로그래밍을 통해서 가져올 수 있도록(programmatic access) 하라.
public class PhoneNumber {
/**
* 전화번호를 문자열로 변환해서 반환한다.
* 문자열은 "(XXX) YYY-ZZZZ'' 형식으로, 14개 문자로 구성된다.
* XXX는 지역번호, YYY는 국번, ZZZZ는 회선 번호다. 각 문자(X, Y, Z)는
* 하나의 숫자다.
*
* 전화번호의 각 필드가 주어진 자리를 다 채우지 못할 경우 필드 앞에는
* 0이 불는다. 예를 들어, 회선 번호가 123일 경우, 위의 문자열 마지막 필드에
* 채워지는 문자열은 "0123"이 된다.
*
* 지역번호를 닫는 괄호와 국번 사이에는 공백이 온다는 것에 주의하자.
*/
@Override
public String toString() {
return String.format("(%03d) %03d-%04d" ,areaCode, prefix, lineNumber);
}
}
규칙 11 clone을 재정의할 때는 신중하라
- Cloneable은 어떤 객체가 복제(clone)를 허용한다는 사실을 알리는 데 쓰려고 고안된 믹스인(mixin) 인터페이스다
- 비-final 클래스에 clone을 재정의할 때는 반드시 super.clone을 호출해 얻은 객체를 반환해야 한다.
- 실질적으로 cloneable 인터페이스를 구현하는 클래스는 제대로 동작하는 pubEc clone 메서드를 제공해야 한다.
- 라이브러리가 할 수 있는 일을 클라이언트에게 미루지 말라는 것이다.
- 사실상, clone 메서드는 또 다른 형태의 생성자다. 원래 객체를 손상시키는 일이 없도록 해야 하고, 복사본의 불변식(invariant)도 제대로 만족시켜야 한다.
- clone의 아키텍처는 변경 가능한 객체를 참조하는 final 필드의 일반적 용법과 호환되지 않는다.
규칙 12 Comparable 구현을 고려하라
- compareTo를 구현할 때는 모든 x와 y에 대해 sgn(x.compareTo(y)) == -sgn(y,compareTo(x))가 만족되도록 해야 한다. (y.compareTo(x)가 예외를 발생시킨다면 x.compareTo(y)도 그래야 하고, 그 역도 성립해야 한다.)
- compareTo를 구현할 때는 추이성(transitivity)이 만족되도록 해야 한다. 즉, (x.compareTo(y)) 0 && y.compareTo(z)) 0)이면 x.compareTo(z)) 0이어야 한다.
- 마지막으로, x.compareTo(y) == 0이면 sgn(x.compareTo(z)) == sgn(y.compareTo(z))의 관계가 모든 z에 대해 성립하도록 해야 한다.
- 강력히 추천하지만 절대적으로 요구되는 것은 아닌 조건 하나는 (x.compareTo(y) == 0) == (x.equals(y))이다.