코딩하는 털보

이펙티브 자바, 아이템 10. equals는 일반 규약을 지켜 재정의하라 본문

Book/이펙티브 자바

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

이정인 2021. 8. 21. 01:02

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

equals 메서드는 다음 상황 중 하나에 해당하면 재정의하지 않는 것이 최선
각 인스턴스가 본질적으로 고유하다. ( 주로 값 클래스가 아닌 동작하는 개체를 표현하는 클래스 ex. Thread )
인스턴스의 '논리적 동치성'을 검사할 일이 없다.
상위 클래스에서 재정의한 equals가 하위 클래스에서도 딱 들어맞는다.
클래스가 private이거나 package-private이고 equals 메서드를 호출 할 일이 없다.

그렇다면 equals를 재정의해야 할 때는 언제인가?
인스턴스간 논리적 동치성을 확인해야 하는데, 상위 클래스의 equals가 논리적 동치성을 비교하도록 재정의되지 않았을 때. (주로 값 클래스 ex. String, Integer, ...)
두 값 객체를 비교하는 프로그래머는 물리적으로 같은 객체인지가 아닌 논리적으로 같은 값인지를 확인하고 싶어 할 것이기 때문이다.
equals가 논리적 동치성을 비교할 수 있으면, Map의 키와 Set의 원소로 그 객체를 사용할 수 있다.

  • equals의 일반 규약 (x, y, z는 null이 아님.)
    • 반사성 : x.equals(x) 는 true
    • 대칭성 : x.equals(y)가 true면 y.equals(x) 도 true
    • 추이성 : x.equals(y)가 true이고 y.equals(z) 도 true이면 x.equals(z) 도 true이다.
    • 일관성 : x.equals(y)를 반본해서 호출하면 항상 true를 반환하거나 항상 false를 반환한다.
    • not-null : x.equals(null)은 false이다. (NullPointerException도 허용하지 않는다.)
  • 리스코프 치환 원칙
    어떤 타입에 있어 중요한 속성이라면 그 하위 타입에서도 마찬가지로 중요하다. 따라서 그 타입의 모든 메서드가 하위 타입에서도 똑같이 잘 작동해야 한다.
  • 상속했을때 equals 규약 지키기
    콘크리트 클래스를 확장해 새로운 값을 추가하면서 equals 규약을 만족시킬 방법은 존재하지 않는다. (대칭성 또는 추이성 위반)
    상속 대신 컴포지션을 사용하면 우회할 수 있다.
  • instanceof를 사용하면 not-null 규약을 쉽게 만족시킬 수 있다.
    null instanceof 처럼 instanceof 의 첫 번째 피연산자가 null인 경우 무조건 false를 반환한다.
  • 양질의 equals 메서드 만들기

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

    • float, double이 아닌 기본 타입은 == 연산자로 비교한다.
    • float, double은 == 연산자가 아닌 Float.compare(), Double.compare() 메서드를 사용한다.
    • 참조 타입은 각각의 equals 메서드를 사용한다.
    • lock 처럼 객체의 논리적 상태와 관련 없는 필드는 비교하면 안 된다.
  • equals를 재정의할 땐 hashCode도 반드시 재정의하자.

  • 너무 복잡하게 해결하려 들지 말자.

  • Object외의 타입을 매개변수로 받는 equals 메서드는 선언하지 말자. (오버라이딩이 아닌 오버로딩이 되어버린다.)

  • AutoValue 프레임워크 : equals 및 hashCode를 재정의해주는 오픈소스.

Comments