이펙티브 자바/3장 모든 객체의 공통 메서드

아이템 10 equals는 일반 규약을 지켜 재정의하라

말랑공룡 2023. 12. 19. 16:19

 

equals를 재정의 할 필요가 없는 상황


  • 각 인스턴스가 본질적으로 고유하다.
    Object의 equals 메서드가 그렇다.
  • 인스턴스의 '논리적 동치성(logical equality)'을 검사할 일이 없다.
    java.util.regex.Pattern의 equals같이 동일한 정규표현식을 나타내는지 같은 논리적 동일함을 판단할 필요가 없다면 Object의 equals를 그대로 받으면 된다.
  • 상위 클래스에서 재정의한 equals가 하위 클래스에도 딱 들어맞는다.
    Set <= AbstractSet, List <= AbstractList, Map <= AbstractMap같이 상속받아 그대로 쓰는 경우.
  • 클래스가 private이거나 package-private이고 equals 메서드를 호출할 일이 없다.

 

equals의 재정의가 필요한 상황


  • 논리적 동치성을 확인해야 할 때
    주로 값 클래스들이 여기 해당한다. (Integer, String처럼 값을 표현하는 클래스)
    아닌 경우도 있다. (Enum)

equals 재정의 규약


* 전제조건: 모든 참조 값 x, y, z는 null이 아니다.

  • 반사성(reflexivity)
    x.equals(x)는 true다.
  • 대칭성(symmetry)
    x.equals(y)가 true면 y.equals(x)도 true다.
  • 추이성(transitivity)
    x.equals(y)가 true이고 y.equals(z)도 true면 x.equals(z)도 true다.
  • 일관성(consistency)
    x.equals(y)를 반복해서 호출하면 항상 true를 반환하거나 항상 false를 반환한다.
  • null-아님
    x.equals(null)은 false다.

 

규약 위배 문제


주로 상속 관계 클래스들끼리 비교할 때, 대칭성이나 추이성이 위배되는 사례가 발생하는데,
(부모에 있는 필드와 자식에만 있는 필드 간의 어긋남)

괜찮은 우회 방법이 하나 있다.

"상속 대신 컴포지션을 사용하라"

 

부모 클래스를 상속하는 대신 부모 클래스를 필드 객체로 두고 그 객체를 비교한다.

 

 

양질의 equals 메서드 구현 방법


  1. == 연산자를 사용해 입력이 자기 자신의 참조인지 확인한다.
    자기 자신이면 true 반환.
  2. instanceof 연산자로 입력이 올바른 타입인지 확인한다.
    그렇지 않다면 false 반환.
  3. 입력을 올바른 타입으로 형변환한다.
  4. 입력 객체와 자기 자신의 대응되는 '핵심' 필드들이 모두 일치하는지 하나씩 검사한다.

 

필드 비교


  • 기본 타입 필드: ==
  • 참조 타입 필드: equals (null 포함일 경우: Objects.equals(Object, Object))
  • float, double: Float.compare(float, float) / Double.compare(double, double)
  • 배열: 원소 각각 비교 혹은 Arrays.equals

 

핵심 정리


꼭 필요한 경우가 아니면 equals를 재정의하지 말자.

많은 경우에 Object의 equals가 여러분이 원하는 비교를 정확히 수행해준다.

재정의해야 할 때는 그 클래스의 핵심 필드 모두를 빠짐없이, 다섯 가지 규약을 확실히 지켜가며 비교해야 한다.