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

Effective Java 3e 아이템 10를 요약한 내용 입니다.

문제를 회피하는 가장 쉬운 길은 아예 재정의하지 않는 것이다. 그냥 두면 그 클래스의 인스턴스는 오직 자기 자신과 같게 된다. 그러니 다음에서 열거한 상황 중 하나에 해당한다면 재정의하지 않는 것이 최선이다.

  • 각 인스턴스가 본질적으로 고유하다. 값을 표현하는 게 아니라 동작하는 개체를 표현하는 클래스가 여기 해당한다. Thread가 좋은 예로, Object의 equals 메서드는 이러한 클래스에 딱 맞게 구현되었다.

  • 인스턴스의 '논리적 동치성'을 검사할 일이 없다.

  • 상위 클래스에서 재정의한 equals가 하위 클래스에도 딱 들어맞는다. 예컨대 대부분의 Set 구현체는 AbstractSet이 구현한 equals를 상속받아 쓰고, List 구현체들은 AbstractList를 상속받아 그대로 사용한다.

  • 클래스가 private이거나 package private이고 equals 메서드를 호출할 일이 없다.

그렇다면 equals를 재정의해야 할 때는 언제일까?

객체 식별성(두 객체가 물리적으로 같은가)이 아니라 논리적 동치성을 확인해야 할 때다. 두 값 객체를 equals로 비교하는 프로그래머는 객체가 같은지가 아니라 값이 같은지를 알고 싶어 할 것이다.

값 클래스라 해도 값이 같은 인스턴스가 둘 이상 만들어지지 않음을 보장하는 인스턴스 통제 클래스라면 equals를 재정의하지 않아도 된다.

equals 메서드는 동치관계를 구현하며 다음을 만족한다.

  • 반사성 : null이 아닌 모든 참조 값 x에 대해, x.equals(y)는 true다.

  • 대치성 : null이 아닌 모든 참조 값 x, y에 대해 x.equals(y)가 true면 y.euqls(x)도 true다.

  • 추이성 : null이 아닌 모든 참조 값 x,y,z에 대해 x.equals(y)가 true이고 y.equals(z)도 true이면 x.equals(z)도 true이다.

  • 일관성 : null이 아닌 모든 참조 값 x,y에 대해 x.equals(y)를 반복해서 호출하면 항상 true를 반환하거나 항상 false를 반환한다.

  • null 아님 : null이 아닌 모든 참조 값 x에 대해 x.equals(null)은 false다.

이 규약을 어기면 프로그램이 이상하게 동작하거나 종료될 것이고, 원인이 되는 코드를 찾기도 굉장히 어려울 것이다.

위의 조건을 토대로 양질의 equals 메서드 구현 방법을 단계별로 정리해보겠다.

  • == 연산자를 사용해 입력이 자기 자신의 참조 인지 확인한다.

  • instanceof 연산자로 입력이 올바른 타입 인지 확인한다. 이때의 올바른 타입은 equals가 정의된 클래스인 것이 보통이지만, 가끔은 그 클래스가 구현한 특정 인터페이스가 될 수도 있다.

  • 입력을 올바른 타입으로 형변환한다. 앞서 2번에서 instanceof를 했기 때문에 이 단계는 100% 성공한다.

  • 입력 객체와 자기 자신의 대응되는 '핵심' 필드들이 모두 일치하는지 하나씩 검사한다.

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

  • equals를 재정의할 땐 hashCode도 반드시 재정의하자(아이템11 참고)

  • Object 외의 타입을 매개변수로 받는 equals 메서드는 선언하지 말자. 이 메서드는 Object.equals를 재정의한 게 아니다. 입력 타입이 Object가 아니므로 재정의가 아니라 다중정의 한것이다.

equals를 작성하고 테스트하는 일은 지루하고 이를 테스트하는 코드도 항상 뻔하다. 다행히 이 작업을 대신해줄 오픈소스가 있으니 바로 구글이 만든 AutoValue 프레임워크다. 클래스에 애너테이션 하나만 추가하면 AutoValue가 이 메서드들을 알아서 작성해주며, 직접 작성하는 것과 근본적으로 똑같은 코드를 만들어줄 것이다.

구글 AutoValue 애노테이션 예제 필요

정리

꼭 필요한 경우가 아니면 equals를 재정의하지 말자. 많은 경우에 Object의 equals가 여러분이 원하는 비교를 정확히 수행해준다. 재정의해야 할 때는 그 클래스의 핵심 필드모두를 빠짐없이, 다섯 가지 규약을 확실히 지켜가며 비교해야 한다.

Last updated