아이템 10. equals 는 일반 규약을 지켜 재정의하라.
1. equals 메서드의 기본 동작
자바의 모든 클래스는 Object 클래스를 상속 받는다.
Object 클래스의 equals 메서드는 참조 동등성(
Reference equality
)을 기준으로 두 객체를 비교한다.
즉, 기본 구현은 두 객체의 메모리 주소가 같은지 확인한다.
논리적 동등성과 재정의의 필요성
그러나, 우리가 객체를 비교할 때 논리적 동등성을 기준으로 비교해야 하는 경우가 많다.
예를 들어, 사용자 정의 클래스에서 이름이나 ID 같은 필드 값을 기준으로 동등성을 정의하고 싶을 수도 있다.
이 경우, equals 메서드를 재정의 해야 한다.
2. equals 메서드의 5가지 규약
2.1. 반사성(Reflexive)
어떤 객체라도 자기 자신과 같아야 한다.
x.equals(x)
는 항상true
여야 한다.
2.2. 대칭성(Symmetric)
x.equals(y)
가true
라면,y.equals(x)
도true
여야 한다.대칭성을 지키지 않으면 객체 간 비교가 일관성을 잃는다.
2.3. 추이성(Transitive)
x.equals(y)
가true
이고,y.equals(z)
도true
라면,x.equals(z)
도true
여야 한다.
2.4. 일관성(Consistent)
x.equals(y)
의 결과는 객체의 상태가 변경되지 않는 한 항상 동일해야 한다.즉, 동일한 입력에 대해 equals 결과가 변하지 않아야 한다.
2.5. null 비교에서도 항상 false
어떤 객체라도 null 과 비교하면
false
를 반환해야 한다.즉,
x.equals(null)
는 항상false
여야 한다.
3. equals 메서드 구현 시 고려사항
3.1. 올바른 재정의의 핵심
참조 동일성 확인
this == obj
를 먼저 확인하여 빠르게 동일 객체인지 판단.
null
확인obj가
null
이면false
를 반환한다.
타입 확인
비교 대상 객체가 같은 클래스인지 확인한다. (
getClass()
또는instanceof
)
주요 필드 비교
객체의 동등성을 결정하는 중요한 필드 값을 비교한다.
3.2. 올바른 재정의 예제
4. 잘못된 equals 구현의 문제
4.1. 대칭성 위반
상속 관계에서 equals 구현이 잘못되면 대칭성이 깨질 수도 있다.
다음과 같은 문제가 발생 한다.
5. hashCode 와의 관계
equals 를 재정의하면 반드시 hashCode 도 재정의 해야 한다.
해시 기반 컬렉션(HashMap, HashSet) 은 hashCode를 사용해 객체를 비교하기 때문에,
hashCode 가 일관되지 않으면 컬렉션에서 의도한 대로 동작하지 않는다.
6. equals와 관련된 실용적인 팁
instanceof 와 getClass() 의 사용
instanceof 를 사용하면 상속된 클래스에서도 equals 를 구현할 수 있지만,
getClass() 를 사용하여 정확히 동일한 클래스만 비교할 수 있다.
중요한 필드만 비교
모든 필드를 비교하면 성능이 저하될 수 있고, 많은 문제를 야기한다.
논리적으로 동등성을 판단하는 데 필요한 필드만 비교한다.
불변 객체에서 유리
불변 객체는 상태가 변경되지 않으므로 equals를 쉽게 구현할 수 있다.
Object.equals 사용
객체 비교 시, Objects.equals(a, b) 를 사용하면 null 체크를 간단히 처리할 수 있다.
7. 결론
equals 메서드를 재정의 해야 하는 상황을 구분하고, 재정의 할 때 반드시 지켜야 할 규약과 올바른 구현 방법을 강조했다.
equals를 잘못 구현하면 논리적 동등성이 깨지고, 해시 기반 컬렉션이나 다른 API 에서 의도한 대로 동작하지 않을 수 있다.
따라서, equals 재정의 시 규약을 철저히 지키고, 항상 hashCode와 함께 재정의 해야 한다.
8. 느낀점
intellij 에서 기계적으로 재정의했던 equals / hashCode 가 왜 필드를 선택하라고 뜨는 지, hashCode 는 왜 기본적으로
재정의 해야 하는 지 알게되었다. 습관적으로 사용하는 행위들도 모두 다 이유를 알아두면 좋을 것 같다는 생각이 들었다.