equals 재정의하지 않는 것이 최선인 경우
- 각 인스턴스가 본질적으로 고유할 때 : Thread처럼 동작하는 개체 표현시
- 인스턴스의 '논리적 동치성(logical equality)'을 검사X : 두 패턴이 일치하는지 검사하지 말 것
- 상위 클래스에서 재정의한 equals가 하위 클래스에도 딱 들어맞는다.
- 클래스가 private이거나 package-private이고 equals 메서드를 호출할 일 X
- Enum이나 인스턴스 통제 클래스처럼 인스턴스가 둘 이상 생성되지 않는다면(불변이라면) 필요X
그렇다면 언제 재정의하쥬?
- 객체의 논리적 동치성을 확인해야 하는데, 상위 클래스의 equals가 이를 지원하지 않을때 재정의해야 함
- 객체가 같은지가 아니라 두 객체의 값이 일치하느냐에 대해 알고 싶을 때
equals는 동치관계를 구현
아래 규약은 모두 null이 아닌 모든 참조값이라는 공통 규약이 있다.
반사성(reflexivity)
- x.equals(x) - true (this같은 객체 본인은 본인과 동일하다는 뜻)
대칭성(symmetry)
- x.equals(y) = true 이면, y.equals(x) = true
추이성(transitivity)
- x.equals(y) = true, y.equals(z) = true 이면 x.equals(z) = true
- 상위 클래스에는 없는 새로운 필드를 가지는 하위클래스 구현시 상위 클래스와 하위클래스 객체 비교를 할 때 추이성이 깨지기 쉬움
- 구체 클래스를 확장해 새로운 값을 추가하면서 equals 규약을 만족시킬 방법은 존재하지 않음
- 리스코프 치환 원칙에서는 어떤 타입에 있어 중요한 속성이라면 그 하위 타입에서도 마찬가지로 중요하므로 그 타입의 모든 메서드가 하위 타입에서도 똑같이 잘 작동해야 한다고 말한다.
- 구체 클래스의 하위 클래스에서 값을 추가할 괜찮은 우회 방법은 상속 대신 컴포지션을 사용하라(item-18)이다.
일관성(consistency)
- x.equals(y) 가 호출시 항상 true or false 하나의 값만을 반환
- 두 객체가 같다면 영원히 같아야 한다.
- equals 판단에 신뢰할 수 없는 자원이 끼어들게 해서는 안됨 ex) URL equals에 매핑되는 호스트 IP 주소 검사까지? 잉? IP주소는 계속 바뀔 수 있음 , url만 검사하는 것이 좋음
null-아님
- x.equals(null) = false가 나와야 함
- instanceof를 사용하면 null검사 코드 추가할 필요 없음
단계별 equals 메서드 구현 방법
- == 연산자를 사용해 입력이 자기 자신의 참조인지 확인
- instanceof 연산자로 입력이 올바른 타입인지 확인
- 입력을 올바른 타입으로 형변환한다.(2단계했으면 바로 통과)
- 입력 객체와 자기 자신의 대응되는 핵심 필드들이 모두 일치하는지 하나씩 검사
- instanceof 에서 인터페이스를 사용했다면 필드값을 가져올 때도 인터페이스 메서드를 사용할 것
마지막 주의사항
- 대칭적인가? 추이성이 있는가? 일관적인가?를 자문해보자.
- equals를 재정의할 땐 hashCode도 반드시 재정의하자(item-11)
- 너무 복잡하게 해결하지말자
public boolean equals(MyClass o){}
이런식으로 Object 외의 타입을 매개변수로 받는 equals 선언 X (MyClass대신 반드시 Object 넣기)
- equals를 재정의하고 테스트하는일이 힘들다면!! 작성하고자 하는 근본적인 코드를 제공해주는 AutoValue를 사용하자
핵심 정리
꼭 필요한 경우가 아니면 equals를 재정의하지 말자. 많은 경우에 Object의 equals가 여러분이 원하는 비교를 정확히 수행해준다. 재정의해야 할 때는 그 클래스의 핵심 필드 모두를 빠짐없이, 다섯 가지 규약을 확실히 지켜가며 비교해야 한다.