3장 — 모든 객체의 공통 메서드

규칙 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))이다.