아래와 같은 상황이면 아예 equals를 재정의하지 말자.
각 인스턴스가 본질적으로 고유하다. ex. Thread
인스턴스의 논리적 동치성을 검사할 일이 없다.
상위 클래스에서 재정의한 equals가 하위 클래스에도 딱 들어맞는다.
클래스가 private 이거나 package-private이고 equals 메서드를 호출할 일이 없다.
클래스가 private 이거나 package-private 이고 equals 메서드를 호출할 일이 없다.
@Override
public boolean equals(Object o) {
throw new AssertionError(); // 호출 금지
}
논리적 동치성을 확인해야 하는데, 재정의도지 않았을 때. 주로 값 클래스들이 여기 해당한다.
값 클래스라 해도, 값이 같은 인스턴스가 둘 이상 만들어지지 않음을 보장하는 인스턴스 통제 클래스(아이템 1)라면 equals를 재정의하지 않아도 된다. Enum(아이템 34)도 여기에 해당한다. 이런 클래스에서는 논리적으로 같은 인스턴스가 2개 이상 만들어지 지지 않으니 논리적 동치성과 식별성이 사실상 똑같은 의미가 된다.
equals 메서드를 재정의할 때는 반드시 일반 규약을 따라야 한다.
null이 아닌 모든 참조 값 x에 대해 x.equals(x)는 true다.
nul이 아닌 모든 참조 값 x, y에 대해, x.equals(y)가 true면 y.equals(x)도 true다.
대소문자를 구별하지 않는 문자열 클래스를 만들었다고 생각해보자.
public final class CaseInsensitiveString {
...
// 대칭성 위배
@Override
public boolean equals(Object o) {
if (o instanceOf CaseInsensitiveString)
return s.equalsIgnoreCase((CaseInsensitiveString) o).s);
if (o instanceOf String) // 한 방향으로만 작동한다
return s.equalsIgnoreCase((String) o);
return false;
}
}
CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
String s = "polish";
cis.equals(s)
는 true
를 반환한다. 문제는 CaseInsenstivieString
의 equals
는 일반 String을 알고 있지만 String
의 equals
는 CaseInsensitiveString
의 존재를 모르는데 있다. 따라서 s.equals(cis)
는 false
를 반환하여 대칭성을 명백히 위반한다.
List<CaseInsensitiveString> list = new ArrayList<>();
list.add(cis);
list.contains(s)
를 호출하면 false가 나온다. OpenJDK 버전이 바뀌거나 다른 JDK에서는 true를 반환하거나 런타임 예외를 던질 수도 있다.
null이 아닌 모든 참조 값 x, y, z에 대해, x.equals(y)가 true이고 y.eqauls(z)도 true면 x.equals(z)도 true다.
구체 클래스를 확장해 새로운 값을 추가하면서 equals 규약을 만족시킬 방법은 존재하지 않는다.
null 이 아닌 모든 참조 값 x, y에 대해 x.equals(y)를 반복해서 호출하면 항상 true를 반환하거나 항상 false를 반환한다.
가변 객체는 비교 시점에 따라 같을 수도 다를 수도 있지만, 불변 객체는 한번 다르면 끝까지 달라야 한다.
equals 판단에 신뢰할 수 없는 자원이 끼어들게 해서는 안된다.
null이 아닌 모든 참조 값 x에 대해,x.equals(null)은 false다.
모든 객체는 null과 같지 않아야 한다.
== 연산자를 사용해 입력이 자기 자신의 참조인지 확인한다. 자기 자신이면 true를 반환한다.
instanceOf 연산자로 입력이 올바른 타입인지 확인한다.
입력을 오바른 타입으로 형변환한다.
입력 개체와 자기 자신의 대응되는 '핵심' 필드들이 모두 일치하는지 하나씩 검사한다.