Java/이펙티브 자바(Effective Java)

[이펙티브 자바(Effective Java)][모든 객체의 공통 메서드] - 10. equals는 일반 규약을 지켜 재정의하라

얄루몬 2022. 4. 12. 18:19

📖본 포스팅은 'Effective java - 조슈아' 님의 책를 보고 작성되었습니다.


[equals를 재정의하지 않아야 하는 상황]

  • 값을 표현하는 것이 아닌 동작하는 개체를 표현하는 클래스의 경우
    • 각 인스턴스는 본질적으로 고유하다. 
    • Thread가 좋은 예시로 Object의 equals 메서드는 이러한 클래스에 딱 맞게 구현되어 있다.
  • 논리적 동치성(logical equals)을 검사할 일이 없다.
    • 논리적 동치성이란? 객체가 같은지를 확인하는 것이 아니라 객체가 가지고 있는 각 값이 같은지를 확인하는 것을 의미한다. 
  • 상위 클래스에서 재정의(Overriding)한 equals가 하위 클래스에 딱 들어 맞는 경우를 의미한다. 
  • 클래스가 private이거나 package-private이고 equals 메서드를 호출할 일이 없는 경우

 

[equals를 재정의해야 하는 상황]

  • 객체 식별성(Object identity)이 아니라 논리적 동치성을 확인해야 하는데, 상위 클래스의 equals가 논리적 동치성을 비교하도록 재정의되지 않았을 경우에 equals를 재정의 해야 한다.
    • 객체 식별성이란? 두 객체가 물리적으로 같은지를 나타내는 것이다.
  • 주로 값 클래스가 equals를 재정의해야 하는 상황이 많다.
    • 객체가 같은지 여부가 아닌 값이 같은지 여부를 알고 싶을 때다.
    • 이를 재정의하면 Map의 키와 Set의 원소로 사용할 수 있게 된다. 
    • 그러나 값 클래스라 해도, 값이 같은 인스턴스가 둘 이상 만들어지지 않음을 보장하는 인스턴스 통제 클래스라면 equals를 재정의하지 않아도 된다.

 

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

동치관계란?

집합을 서로 같은 원소들로 이루어진 부분집합으로 나누는 연산으로 이 부분을 동치류(equivalence class)라고 한다.

equals 메서드가 쓸모 있기 위해서는 모든 원소가 같은 동치류에 속한 어떤 원소와도 서로 교환할 수 있어야 한다.

이 동치 관계를 만족시키기 위한 다섯 요건은 아래와 같다. 

[동치관계를 만족시키기 위한 다섯가지 요건]

  • 반사성
    • null이 아닌 모든 참조 값 x에 대해, x.equals(x)는 true이다.
    • 쉽게 말해서 반사성은 객체는 자기 자신과 같이야 한다. 이 요건의 경우 일부러 어기는 경우가 아니라면 만족시키지 못하는 것이 더 어렵다.
  • 대칭성
    • null이 아닌 모든 참조 값 x,y에 대해, x.equals(y)가 true면 y.equals(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다.
    • 모든 객체가 null과 같지 않아야 한다는 뜻이다. 

 

[equals 메서드 구현 방법]

  1. ==연산자를 사용해 입력이 자기 자신의 참조인지 확인한다.
    • 자기 자신이면 true를 반환한다. 이는 단순 성능 최적화용으로 비교 작업이 복잡한 상황에 값어치를 할 것이다.
    • float과 double을 제외한 기본 타입 필드는 == 연산자로 비교한다. 
    • 참조 타입 필드는 각각의 equals 메서드로 비교한다.
    • float과 double 필드는 각각 정적 메서드인 Float.compare(float,float)와 Double.compare(double, double)로 비교한다. 
  2. instanceof 연산자로 입력이 올바른 타입인지 확인한다.
    • 올바른 타입이 아니라면 false를 반환할 것이다.
  3. 입력을 올바른 타입으로 형변환한다.
    • instanceof 검사를 했기 때문에 이는 100% 성공한다.
  4. 입력 개체와 자기 자신의 대응되는 핵심 필드들이 모두 일치하는지 하나씩 검사한다. 
    • 모든 필드가 일치할 땐 true 하나라도 다르면 false를 반환한다. 

 

[핵심 정리]

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