Java ‐ equals는 일반 규약을 지켜 재정의하라[Effective Java Item 10] - dnwls16071/Backend_Summary GitHub Wiki
equals는 일반 규약을 지켜 재정의하라
- 재정의할 필요가 없는 경우
- 각 인스턴스가 본질적으로 고유하다.
- 값을 표현하는게 아니라 동작하는 객체 자체를 표현하는 클래스가 여기에 해당되는데 대표적으로 Thread가 있다.
- 인스턴스의 논리적 동치성을 검사할 일이 없다.
- 상위 클래스에서 재정의한 equals가 하위 클래스에도 들어맞는다.
- 클래스가 private이거나 package-private이면 equals() 메서드를 호출할 일이 없다.
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이다.
equals 메서드 구현 방법은?
- == 연산자를 사용해 입력이 자기 자신의 참조인지 확인한다.
- 자기 자신이라면 true를 반환한다.
- 단순한 성능 최적화용도이며, 비교 작업이 복잡한 상황에서 요긴하게 사용된다.
- instanceof 연산자로 입력이 올바른 타입인지 확인한다.
- 입력과 호환되는 타입이 아니라면 false를 반환한다.
- 입력을 올바른 타입으로 형변환한다.
- 입력 객체와 자기 자신의 대응되는 핵심 필드들이 모두 일치하는지 하나씩 검사한다.
- 모든 필드가 일치하면 true를, 하나라도 다르면 false를 반환한다.
// Good
public final class PhoneNumber {
private final short areaCode;
private final short prefix;
private final short lineNumber;
public PhoneNumber(int areaCode, int prefix, int lineNumber) {
this.areaCode = (short) areaCode;
this.prefix = (short) prefix;
this.lineNumber = (short) lineNumber;
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof PhoneNumber)) {
return false;
}
PhoneNumber pn = (PhoneNumber) o;
return pn.areaCode == areaCode && pn.prefix == prefix && pn.lineNumber == lineNumber;
}
@Override
public int hashCode() {
int result = Short.hashCode(areaCode);
result = 31 * result + Short.hashCode(prefix);
result = 31 * result + Short.hashCode(lineNumber);
return result;
}
}
- equals를 재정의할 땐 반드시 hashCode도 재정의해야한다.
- 재정의된
equals() 내부에서 필드들의 동치성만 검사해도 equals 규약을 지킬 수 있다.
- 꼭 필요한 경우가 아니라면 equals를 재정의하지 말자.
- 논리적 동치성 = 객체가 가지고 있는 값이 같은지!
- 물리적 동치성 = 객체의 메모리 주소가 같은지!