[이펙티브 자바] 10. equals는 일반 규약을 지켜 재정의하라

1. 개념

equals를 단순히 재정의 하는 것은 쉽지만 함정이 많다. 이번 장에서는 equals를 재정의 할 때 고려해야 하는 점과, 재정의가 완료된 후 확인해야 하는 부분들에 대해 다루고 있다.

2. equals를 재정의하는 경우와 재정의하지 말아야 하는 경우

2-1. equals를 재정의하지 말아야 할 경우

  • 각 인스턴스가 본질적으로 고유한 값을 표현하는 클래스: 예를 들어, Thread 클래스는 각 인스턴스가 고유한 ID를 가지므로 equals를 재정의할 필요가 없다.
  • 논리적 동치성 검사가 필요 없는 경우: 대부분의 경우 객체 식별성만 중요하며, 논리적 동치성은 필요하지 않을 수 있다.
  • 상위 클래스에서 재정의한 equals가 하위 클래스에 적절한 경우: 상위 클래스에서 이미 equals를 적절히 구현했고, 이를 하위 클래스에서 그대로 사용해도 무방한 경우이다.
  • 접근이 제한된 클래스: equals를 호출할 일이 없는 private이나 package-private 클래스일 경우, 재정의할 필요가 없다.

2-2. equals를 재정의해야 할 경우

  • 논리적 동치성 비교가 필요한 경우: 객체의 내용이 같은지를 비교해야 할 때, 상위 클래스의 equals가 이를 충족시키지 않을 때 재정의가 필요하다.

3. equals 메서드의 규약

Object 기본 명세의 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(z)도 true이면 x.equals(z)도 true여야 한다.
  • 일관성 (Consistency): 모든 null이 아닌 참조 값 x, y에 대해, x.equals(y)를 반복해서 호출하면 항상 true 또는 항상 false를 반환해야 한다.
  • null-아님 (Non-nullity): 모든 null이 아닌 참조 값 x에 대해, x.equals(null)은 항상 false여야 한다.

4. equals 구현 방법

  • == 연산자를 사용하여 입력이 자기 자신의 참조인지 확인한다.
  • instanceof 연산자로 입력이 올바른 타입인지 확인한다.
  • 입력을 올바른 타입으로 형변환한다.
  • 입력 객체와 자기 자신의 대응되는 핵심 필드들이 모두 일치하는지 하나씩 검사한다.

5. equals 구현 후 점검

새로 구현된 equals 메서드가 대치성, 추이성, 일관성을 유지하는지 확인해야한다.

6. equals를 재정의하는 예

경험했던 equals를 재정의하는 예로는 보통 값 객체(value object, VO)가 있다. 예를 들어 User 클래스에서 식별자 id가 같으면 같은 사용자로 간주할 수 있다. 이 경우, equals를 id 필드를 기준으로 재정의하여 사용자 객체의 논리적 동치성을 정확히 비교할 수 있다.

7. 정리 

  • 꼭 필요한 경우가 아니면 equals를 재정의하지 말아야 한다. Object의 기본 equals 메서드가 대개 원하는 비교를 수행해 준다.
  • 꼭 필요한 경우라면 앞서 정리한 다섯 가지 규약을 반드시 지켜가며 equals를 구현해야 한다.