핵심 정리: equals를 재정의 하지 않는 것이 최선
다음의 경우에 해당한다면 equals를 재정의 할 필요가 없다.
각 인스턴스가 본질적으로 고유하다.
인스턴스의 ‘논리적 동치성’을 검사할 필요가 없다.
상위 클래스에서 재정의한 equals가 하위 클래스에도 적절하다.
클래스가 private이거나 package-private이고 equals 메서드를 호출할 일이 없다
핵심 정리: equals 규약
반사성: A.equals(A) == true → 보이는 모습 그대로
대칭성: A.equals(B) == B.equals(A)
// 대칭성 위배!
@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;
}
// 문제 시연 (55쪽)
public static void main(String[] args) {
CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
// CaseInsensitiveString cis2 = new CaseInsensitiveString("polish");
String polish = "polish";
System.out.println(cis.equals(polish));
// System.out.println(cis2.equals(cis));
List<CaseInsensitiveString> list = new ArrayList<>();
list.add(cis);
System.out.println(list.contains(polish));
}
추이성: A.equals(B) && B.equals(C), A.equals(C)
// // 코드 10-3 잘못된 코드 - 추이성 위배! (57쪽)
@Override public boolean equals(Object o) {
if (!(o instanceof Point))
return false;
// o가 일반 Point면 색상을 무시하고 비교한다.
if (!(o instanceof ColorPoint))
return o.equals(this);
// o가 ColorPoint면 색상까지 비교한다.
return super.equals(o) && ((ColorPoint) o).color == color;
}
public static void main(String[] args) {
// 첫 번째 equals 메서드(코드 10-2)는 대칭성을 위배한다. (57쪽)
// Point p = new Point(1, 2);
// ColorPoint cp = new ColorPoint(1, 2, Color.RED);
// System.out.println(p.equals(cp) + " " + cp.equals(p));
// 두 번째 equals 메서드(코드 10-3)는 추이성을 위배한다. (57쪽)
ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
Point p2 = new Point(1, 2);
ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
System.out.printf("%s %s %s%n",
p1.equals(p2), p2.equals(p3), p1.equals(p3));
}
===
// CounterPoint를 Point로 사용하는 테스트 프로그램
public class CounterPointTest {
// 단위 원 안의 모든 점을 포함하도록 unitCircle을 초기화한다. (58쪽)
private static final Set<Point> unitCircle = Set.of(
new Point( 1, 0), new Point( 0, 1),
new Point(-1, 0), new Point( 0, -1));
public static boolean onUnitCircle(Point p) {
return unitCircle.contains(p);
}
public static void main(String[] args) {
Point p1 = new Point(1, 0);
Point p2 = new CounterPoint(1, 0);
// true를 출력한다.
System.out.println(onUnitCircle(p1));
// true를 출력해야 하지만, Point의 equals가 getClass를 사용해 작성되었다면 그렇지 않다.
System.out.println(onUnitCircle(p2));
}
}
===
// 잘못된 코드 - 리스코프 치환 원칙 위배! (59쪽)
// @Override public boolean equals(Object o) {
// if (o == null || o.getClass() != getClass())
// return false;
// Point p = (Point) o;
// return p.x == x && p.y == y;
// }
@Override public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Point)) {
return false;
}
Point p = (Point) o;
return p.x == x && p.y == y;
}
public class CounterPointTest {
Point p2 = new pakacge route.(1, 0, Color.RED).asPoint();
// 코드 10-5 equals 규약을 지키면서 값 추가하기 (60쪽)
public class ColorPoint {
private final Point point;
private final Color color;
public ColorPoint(int x, int y, Color color) {
point = new Point(x, y);
this.color = Objects.requireNonNull(color);
}
/**
* 이 ColorPoint의 Point 뷰를 반환한다.
*/
public Point asPoint() {
return point;
}
@Override public boolean equals(Object o) {
if (!(o instanceof ColorPoint))
return false;
ColorPoint cp = (ColorPoint) o;
return cp.point.equals(point) && cp.color.equals(color);
}
@Override public int hashCode() {
return 31 * point.hashCode() + color.hashCode();
}
}
일관성: A.equals(B) == A.equals(B)
null-아님: A.equals(null) == false
핵심 정리: equals 구현 방법