@Override
public boolean equals (Object o){
throw new AssertionError(); // 호출 금지
}
- 객체는 자기 자신과 같아야 한다.
- 두 객체는 서로에 대한 동치 여부에 똑같이 답해야 한다.
// 대칭성 위배!
@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;
}
//올바르게 수정, instanceof String 부분 삭제
@Override public boolean equals(Object o) {
return o instanceof CaseInsensitiveString &&
((CaseInsensitiveString) o).s.equalsIgnoreCase(s);
}
- 첫 번째 객체와 두 번째 객체가 같고, 두 번째 객체와 세 번째 객체가 같다면, 첫 번째 객체와 세 번째 객체도 같아야 한다는 뜻이다.
1 . 대칭성 위배
// ColorPoint의 equals
@Override public boolean equals(Object o) {
if (!(o instanceof ColorPoint))
return false;
return super.equals(o) && ((ColorPoint) o).color == color;
}
Point p = new Point(1, 2);
ColorPoint cp = new ColorPoint(1, 2, Color.RED);
System.out.println(p.equals(cp) + ", " + cp.equals(p)); //true(Point의 equals), false(ColorPoint의 equals)
2 . 추이성 위배
// ColorPoint의 equals
@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;
}
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)); //true(2번째 if문에서 Point의 equals), true(Point의 equals), false(ColorPoint의 equals)
3 . 무한 재귀 문제
//SmellPoint의 equals
@Override public boolean equals(Obejct o){
if(!(o instanceof Point))
return false;
if(!(o instanceof SmellPoint))
return o.equals(this);
return super.equals(o) && ((SmellPoint) o).color == color;
}
ColorPoint p1 = new ColorPoint(1,2, Color.RED);
SmellPoint p2 = new SmellPoint(1,2);
p1.equals(p2);
// 처음에 ColorPoint의 equals > 2번째 if문 걸리고 SmellPoint의 equals 비교
// SmellPoint의 equals > 2번쨰 if문 ColorPoint의 equals
// 무한 재귀 > StackOverflowError
@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;
}
- Point의 하위클래스는 정의상 여전히 Point이므로 어디서든 Point로 활용가능해야 한다.
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();
}
}
- 두 객체가 같다면 앞으로도 영원히 같아야 한다는 뜻이다.
- 클래스를 만들 때는 불변 클래스로 만드는 게 나을지 심사숙고하자(ITEM17)
- 불변 클래스로 만들기로 했다면 한 번 같은 것은 계속 같고, 한 번 다른 것은 계속 달라야 한다.
- 모든 객체가 null과 같지 않아야 한다는 뜻이다.
1 . 명시적 null 검사
@Override public boolean equals(Object o){
if( o == null){
return false;
}
}
2 . 묵시적 null 검사
@Override public boolean equals(Obejct o){
if(!(o instanceof MyType))
return false;
MyType mt = (MyType) o;
}
1 . == 연산자를 사용해 입력이 자기 자신의 참조인지 확인한다.
2 . instanceof 연산자로 입력이 올바른 타입인지 확인한다.
- EX) Set, List, Map, Map.Entry 등의 컬렉션 인터페이스
3 . 입력을 올바른 타입으로 형변환한다.
4 . 입력 객체와 자기 자신의 대응되는 ‘핵심’ 필드들이 모두 일치하는지 하나씩 검사한다.
- Float.equals(float)나 Double.equals(double) 은 오토 박싱을 수반할 수 있어 성능상 좋지 않다.
- 모두가 핵심 필드라면 Arrays.equals() 메소드 사용
- 불변 클래스에 제격
public boolean equals(MyClass o){
//... 잘못된 예, 입력 타입은 반드시 Object
}
@AutoValue
만 추가하면 알아서 작성해준다.Reference: