Objects의 non-final 메서드에 대해 알아봅니다.
'논리적 동치성'을 확인해야 하는데, 상위 클래스의 equals가 논리적 동치성을 비교하도록 재정의되지 않았을 때
객체는 자기 자신과 같아야 한다.
x.equals(x) = True
두 객체는 서로에 대한 동치 여부에 똑같이 답해야 한다.
x.equals(y) = True, y.equals(x) = True
ex) CaseInsensitiveString.equals(String) != String.equals(CaseInsensitiveString)
x.equals(y) = True, y.equals(z) = True, x.equals(z) = True
ex) ColorPoint1.equals(Point) = True, Point.equals(ColorPoint2) = True, ColorPoint1.equals(ColorPoint2) = False
구체 클래스를 확장해 새로운 값을 추가하면서 equals 규약을 만족시킬 방법은 존재하지 않는다.
-> 상속 대신 컴포지션을 사용하면 가능하다.
두 객체가 같다면 앞으로도 영원히 같아야 한다.
x.equals(y) = True.. True.. True...
ex) URL의 equals는 비교대상인 IP주소를 알기위해서 네트워크를 통과해야 하므로 일관성을 지키지 못할 수도 있다.
모든 객체가 null과 같지 않아야 한다.
x.equals(null) = False
instanceof를 사용하면 null일 때 false를 반환해주므로 null 검사를 명시적으로 하지 않아도 된다.
public class PhoneNumber {
private final short areaCode, prefix, lineNum;
public PhoneNumber(int areaCode, int prefix, int lineNum) {
this.areaCode = rangeCheck(areaCode, 999, "지역코드");
this.prefix = rangeCheck(prefix, 999, "프리픽스");
this.lineNum = rangeCheck(lineNum, 9999, "가입자 번호");
}
private static short rangeCheck(int val, int max, String arg) {
if(val < 0 || val > max) {
throw new IllegalArgumentException(arg + ": " + val);
}
return (short) val;
}
@Override
public boolean equals(Object o) {
if(o == this) {
return true;
}
if(!(o instanceof PhoneNumber)) {
return false;
}
PhoneNumber pn = (PhoneNumber) o;
return pn.lineNum == lineNum && pn.prefix == prefix
&& pn.areaCode == areaCode;
}
}
그렇지 않으면 인스턴스를 HashMap 이나 HashSet 같은 컬렉션의 원소로 사용할 때 문제가 발생한다.
@Override
public int hashCode() {
int result = Integer.hashCode(areaCode); // 1번
result = 31 * result + Integer.hashCode(prefix); // 2번
result = 31 * result + Integer.hashCode(lineNum); // 2번
return result;
}
31을 곱하는 이유는 비슷한 필드가 여러개일 때 해시효과를 크게 높여주기 위해서다.
비슷한 값들이 여러개 있을때 그냥 더하면 같은 값이 나올 확률이 높다.
그래서 31을 곱해 큰수로 만들어 해시효과를 증가시킨다.
클래스가 불변이고 해시코드를 계산하는 비용이 크다면, 매번 새로 계산하기 보다 캐싱을 고려해야 한다.
→ 이 타입의 객체가 주로 해시의 키로 사용될 것 같다면 인스턴스가 만들어질 때 해시코드를 계산해 둔다.
해시의 키로 사용되지 않는 경우라면 hashCode 가 처음 불릴 때 계산하는 지연 초기화를 사용하는 것이 좋다.
→ 필드를 지연 초기화 하려면 그 클래스가 thread-safe가 되도록 동기화에 신경 쓰는 것이 좋다.
Object의 기본 toString 메서드는 PhoneNumber@adbbd처럼 단순히 클래스_이름@16진수로_표시한_해시코드
를 반환한다.
toString의 일반 규약에 따르면 '간결하고 사람이 읽기 쉬운 형태의 유익한 정보'를 반환해야한다.
toString의 규약은 '모든 하위 클래스에서 이 메서드를 재정의하라'고 합니다.
toString을 잘 구현한 클래스는 사용하기 편하고 디버깅하기 쉽다.
실전에서 toString 그 객체가 가진 주요 정보 모두를 반환하는게 좋다.
toString을 구현할 때면 반환값의 포맷을 문서화할지 정해야 한다.
포맷을 명시하든 아니든 주석을 이용하여 개발자의 의도는 명확히 밝혀야한다.
포맷 명시 여부와 상관없이 toString이 반환한 값에 대해 포함된 정보를 얻어올 수 있는 API를 제공해야한다.