본문 바로가기
Books/Effective-Java 3판

10.equals는 일반 규약을 지켜 재정의하라(1)

by 두두리안 2021. 9. 28.
728x90
equals 메서드는 재정의하기 쉬워 보이지만 곳곳에 함정이 도사리고 있어 끔찍한 결과를 초래할 수 있다
문제를 회피하는 가장 쉬운길은 아예 재정의 하지 않는 것이다 (4가지 방법)

1. 각 인스턴스가 본질적으로 고유하다

- 값을 표현하는게 아니라 동작하는 개체를 표현하는 클래스가 여기에 해당한다

- ex) Thread 좋은 예로, Object의 equals 메서드는 이러한 클래스에 딱 맞게 구현돼 있다

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

- java.util.regex.Pattern은 equals를 재정의해서 두 Pattern의 인스턴스가 같은 정규 표현식을 나타내는지를 검사하는,

   즉 논리적 동치성을 검사하는 방법도 있다

- 하지만 설계자는 클라이언트가 이 방식을 원하지 않거나 애초에 필요하지 않다고 판단할 수 있다

- 설계자가 후자로 판단했다면 Object 의 기본 equals만으로 해결된다

3. 상위 클래스에서 재정의한 equals가 하위 클래스에도 딱 들어맞는다

- 대부분 Set 구현체는 AbstractSet이 구현한 equals를 상속받아 쓴다

- List 구현체들은 AbstractList로부터, Map 구현체들은 AbstractMap으로부터 상속받아 그래도 쓴다

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

- equals가 실수로라도 호출되는 걸 막고 싶다면 다음처럼 구현하자

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

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

- 객체 식별성이 아니라 논리적 동치성을 확인해야 하는데, 상위 클래스의 equals가 논리적 동치성을 비교하도록 재정의 되지 않을 때다

- 주로 값 클래스가 여기에 해당한다 (Integer , String)

- 두 값 객체를 equals로 비교하는 프로그래머는 값은 같은지를 알고 싶어 한다

- equals가 논리적 동치성을 확인하도록 재정의 해두면, 그 인스턴스는 값을 비교하길 원하는 프로그래머의 기대에 부응

   Map의 키와 Set의 원소로 사용할 수 있게 된다


equals 메서드를 재정의할 때는 반드시 일반 규약을 따라야 한다

1. 반사성 : null이 아닌 모든 참조 값 x에 대해, x.equals(x)는 true다.
2. 대칭성 : null이 아닌 모든 참조 값 x, y에 대해, x.equals(y)가 true면 y.equals(x)도 true다.
3. 추이성 : null이 아닌 모든 참조 값 x, y, z에 대해 x.equals(y)가 true이고 y.equals(z)도 true면 x.equals(z)도 true다.
4. 일관성 : null이 아닌 모든 참조 값 x, y에 대해 x.equals(y)를 반복해서 호출하면 항상 true를 반환하거나 항상 false를 반환한다.
5.null-아님 : null이 아닌 모든 참조 값 x에 대해, x.equals(null)은 false다. 

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


Object 명세에서 말하는 동치 관계란 무엇일까?

- 집합을 서로 같은 원소들로 이뤄진 부분집합으로 나누는 연산

- 이 부분집합을 동치류(동치 클래스)라 한다.

 

728x90