본문 바로가기

독후감/Effective Java

10. equals는 규칙에 맞게 재정의하라

equals 메서드는 재정의하기 쉬워 보이지만 사실 문제가 많다.
문제를 회피하는 가장 쉬운 길은 아예 재정의하지 않는 것이다.

클래스에 equals를 정의하지 않는다면 그 클래스의 인스턴스는 오직 자기 자신과만 같게된다.
그러니 다음에서 열거한 상황 중 하나에 해당한다면 재정의하지 않는 것이 최선이다.

  • 각 인스턴스가 본질적으로 고유해야 한다.
  • 인스턴스의 동등성을 검사할 필요가 없다.
  • 상위 클래스에서 재정의한 equals가 하위 클래스에도 딱 들어맞는다.
  • 클래스가 private이거나 package-private이고, equals를 호출할 일이 없다.

만약 equals메서드에 대한 실수를 좀 더 철저히 막고 싶다면

@Override
public boolean equals(Object o) {
    throw new AssertionError(); // 호출 금지
}

그렇다면 equals를 재정의해야할 때는 언제일까?
객체에 대한 동등성을 확인해야 하는데, 상위 클래스의 equals로는 동등성을 비교할 수 없을 때이다.

String 처럼 주로 값을 표현하는 클래스가 해당된다.
equals 메서드를 재정의할 때는 반드시 아래 규칙에 맞게 작성해야 한다.

  • 반사성(reflexivity) : null이 아닌 모든 참조 값 x에 대해, x.equals(x)는 true이다.
  • 대칭성(symmetry) : null이 아닌 모든 참조 값 x, y에 대해, x.equals(y)는 true이면 y.equals(x)도 true이다.
  • 추이성(transitivity) : null이 아닌 모든 참조 값 x, y, z에 대해, x.equals(y)는 true이고 y.equals(x)도 true이면 z.equals(x)도 true이다.
  • 일관성(consistency) : null이 아닌 모든 참조 값 x에 대해, x.equals(x)에 대한 결과는 항상 같아야한다.
  • null 아님 : null이 아닌 모든 참조 값 x에 대해, x.equals(null)에 대한 결과는 항상 false이다.

좋은 equals 메서드를 구현하는 방법은 아래와 같다.

  1. == 연산자를 사용해 입력이 자기 자신의 참조인지 확인한다.
  2. instanceof 연산자로 입력이 올바른 타입인지 확인한다.
  3. 입력을 올바른 타입으로 형변환한다. (사실 2번에서 타입검사를 했기에 100% 통과한다.)
  4. 입력 객체와 자기 자신의 핵심 필드들이 모두 일치하는지 검사한다.
  5. equals를 재정의할 때 hashCode도 반드시 재정의하자.

어떤 필드를 먼저 비교하는냐에 equals의 성능이 좌우되기도 한다.
최상의 성능을 바란다면 다를 가능성이 크거나, 비교하는 비용이 싼 필드를 먼저 비교하자.

다음은 좋은 equals에 대한 예제이다.

public final class PhoneNumber {
    private final short areaCode, prefix, lineNum;
 
    public PhoneNumber(short areaCode, short prefix, short lineNum) {
        // ...
    }
 
    @Override
    public boolean equals(Object o) {
        if (o == this)
            return true;
 
        if (!(o instanceof PhoneNumber))
            return false;
 
        PhoneNumber pn = (PhoneNumber)o;
 
        return pn.areaCode == this.areaCode &&
               pn.prefix == this.prefix &&
               pn.lineNum == this.lineNum;
    }
}

equals 메서드를 직접 작성하는 것은 매우 지루하다.
다행히도 equals 생성을 대신해줄 오픈소스들이 있으니 해당 오픈소스들을 적극 활용하자.(Lombok, AutoValue ...)