[Effective Java 3/e] 아이템 10 - equals는 일반 규약을 지켜 재정의하라


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

아래 상황 중 하나에 해당한다면 재정의하지 않는 것이 최선이다

  • 각 인스턴스가 본질적으로 고유하다.
  • 인스턴스의 ‘논리적 동치성(logical equality)’을 검사할 일이 없다.
  • 상위 클래스에서 재정의한 equals가 하위 클래스에도 딱 들어맞는다.
    • ex) Set 구현체 AbstractSet이 구현한 equals 상속받아 사용, List 구현체는 AbstractList 상속, Map 구현체는 AbstractMap 상속
  • 클래스가 private이거나 package-private이고 equals 메서드를 호출할 일이 없다.

실수로라도 equals 호출 되는것을 막는 방법

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

equals 재정의 할 때

  • 논리적 동치성을 확인해야 할 때
    • 값 클래스들이 해당

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.eqauls(z)도 true 이면 x.eqauls(z)도 true
    • 첫 번째 객체와 두 번쨰 객체가 같고, 두 번째 객체와 세 번째 객체가 같다면, 첫 번째 객체와 세 번쨰 객체도 같아야 함
  • 일관성(consistency) : null이 아닌 모든 참조 값 x,y에 대해 x.equals(y)를 반복 호출하면 항상 true 이거나 false 를 반환
    • 두 객체가 같다면 영원히 같아야 함
  • null 아님 : null이 아닌 모든 참조 값 x에 대해, x.equals(null)은 false
    • 모든 객체가 null과 같지 않아야 함

equals 단계별 구현 방법

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

전형적인 equals 메서드의 예

public final class PhoneNumber {
  private final int areaCode, prefix, lineNum;

  public PhoneNumber(int areaCode, int prefix, int lineNum) {
    this.areaCode = areaCode;
    this.prefix = prefix;
    this.lineNum = lineNum;
  }

  @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;
  }
}

equals 재정의 시 주의사항

  • equals 재정의 시 hashCode도 반드시 재정의
  • 너무 복잡하게 해결하려 하지 말자.
    • 필드들의 동치성만 검사해도 됨
  • Object 외의 타입을 매개변수로 받는 equals 메서드는 선언하지 말자.
// 잘못된 예- 입력 타입은 반드시 Object여야 한다.
public boolean equals(MyClass o) {
  ...
}