equals는 일반 규약을 지켜 재정의하라
equals 메서드를 재 정의 할떄는 반드시 정의된 규약을 지키며 재 정의를 해야한다.
만약 이 규약을 어기면 그 객체를 사용하느 다른 객체들이 어떻게 반응할지 알 수 없다.
equals 메서드의 일반 규약
equals 메서드는 동치관계를 구현하며 다음을 만족한다.
반사성(reflexlvity)
반사성은 null이 아닌 모든 참조 값 x에대해, x.equals(x)는 true이다.
단순히 말하면 객체는 자기 자신과 비교했을떄 항상 같아야한다는 것이다.
대칭성(symmetry)
대칭성은 null이 아닌 모든 참조값 x,y에 대해 x.equals(y)가 true면 y.equals(x)는 true이다.
두 객첸느 서로에 대한 동치 여부에 대해 똑같이 대답 해야한다는 뜻이다.
public final class CaseInsensitiveString {
private final String s;
public CaseInsensitiveString(String s) {
this.s = Objects.requireNonNull(s);
}
// 대칭성 위배!
@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
s.equals(cin); // false
이 문제를 해결 하려면 CaseInsensitiveString의 equals를 String의 equals와 연동하겠다는 허황한 꿈을 버려야 한다.
@Override public boolean equals(Object o) {
return o instanceof CaseInsensitiveString &&
((CaseInsensitiveString) o).s.equalsIgnoreCase(s);
}
추이성(transitivity)
추이성은 null이 아닌 모든 참조값 x,y,z에 대해 x.equals(y)가 true고 y.equals(z)가 true면 x.equals(z)도 true이다.
첫번째 객체와 두번째 객체의 값이 같다면 첫번째와 세번째 값도 같아야 한다는 뜻이다.
다만 구체클래스를 확장해 새로운 값을 추가하면서 equals를 지키는 방법은 존재 하지않으며 만약 만족하고 싶다면 상속되신 컴포지션을 사용하는게 좋다.
ColorPoint p1 = new ColorPoint(1,2,Color.RED);
Point p2 = new Point(1,2);
ColorPoint p3 = new colorPoint(1,2,Color.BLUE);
p1.equals(p2); // true
p2.equals(p3); // true
p1.equals(p3); // false
p1, p2, p3는 동일성을 보장하지 못한다.
equals 메서드는 동일 객체 여부만 비교하는데 사용하도록 하자.
일관성(consistency)
일관성은 null이 아닌 모든 참조값 x,y에 대해 x.equals(y)를 반복해서 호출하면 항상 true 를 반환하거나 false를 반환한다.
가변 객체는 비교 시점에 따라 값이 달라질 수 있지만 불변 객체는 반드시 계속 같아야 한다.
두 객체를 비교했을때 어느 한쪽의 값이 수정되거나 변하지 않는다면 앞으로도 계속 같은 값을 반환 해야한다.
null-아님
null-아님은 null이 아닌 모든 참조값 x에대해, x.equals(null)은 false이다.
명시적으로 null을 검사하는것보단 묵시적으로 null 검사하는게 좋다.
// 명시적 null 검사 - 필요 없다!
@Override public boolean equals(Object o) {
if (o == null)
return false;
...
}
// 묵시적 null 검사 - 이쪽이 낫다.
@Override public boolean equals(Object o) {
if (!(o instanceof MyType))
return false;
MyType mt = (MyType) o;
...
}
equals 메서드를 재 정의 하지 않아도 되는 상황
각 인스턴스가 본질적으로 고유할때
값을 표현하는 게 아니라 동작하는 개체를 표현하는 클래스일때 ( service, controller 와 같은 객체에서는 값을 표현하고 검증하지 않으니 재 정의 할 필요 없다)
인스턴스의 '논리적 동치성(logical equality)'을 검사할 일이 없을때
상위 클래스에서 재 정의한 equals메서드가 하위 클래스에서도 딱 들어 맞을때
Set 구현체는 AbstractSet이 구현한 equals를 상속 받아쓰고 List 구현체들은 AbstractList로부터 Map 구현체들은 AbstractMap으로 부터 상속 받아 그대로 쓴다.
클래스가 prviate이거나 package-private고 equals를 호출할 일이 없을때
equals 메서드를 실수로라도 호출하는 일을 막으려면 Error를 발생시키면 된다.
@Override public boolean equals(Object o) {
throw new AssertionError(); // 호출 금지!
}
equals 메서드를 재 정의 해야하는 상황
객체 식별성(Object identity) 이 아니라 논리적으로 동치성을 확인 해야할떄
값 클래스의 Integer, String 처럼 값을 주로 표현하는 클스를 사용할때, 두 값이 동일한지 비교를 위해 equals 메서드를 사용하는 프로그래머 기대의 부응할때 eqauls를 재 정의 한다.
값 클래스라 해도, 값이 같은 인스턴스가 둘 이상 만들어지지 않음을 보장하는 인스턴스 일때
이떄는 equals를 재 정의 하지 않아도 된다.
결론
equals 메서드는 정말 필요할때가 아니라면 재 정의 하지 않는게 좋으며, equals메서드를 재 정의 할떄는 반드시 hashCode 메서드도 재 정의 해야한다.
'book review > Effective java' 카테고리의 다른 글
[Effective Java] Item 12. toString을 항상 재 정의하라 (0) | 2024.04.11 |
---|---|
[Effective Java] Item 11. equals를 재정의하려거든 hashCode도 재정의 해라 (0) | 2024.03.18 |
[Effective Java] Item 9. try-finally보다는 try-with-resources를 사용하라 (0) | 2024.02.14 |
[Effective Java] Item 8. finalizer와 cleaner 사용을 피하라 (0) | 2024.02.13 |
[Effective Java] Item 7. 다 쓴 객체 참조를 해제하라 (1) | 2024.02.07 |