이 글은 Effective Java 3/E의 내용을 요약한 글입니다. 자세한 내용은 책을 참고하시기 바랍니다.
java의 Object들에 재정의를 하는 equals의 경우 잘못 사용하면 끔찍한 결과를 초래할 수 있습니다. 그래서 equals는 객체의 논리적 동치성을 확인해야할 때 빼고 불필요한 경우에는 재정의를 하지 않는 것이 더 좋습니다.
equals를 재정의하지 않아도 되는 상황
- 각 인스턴스가 본질적으로 고유한 경우
- 인스턴스의 논리적 동치성(logical equality)를 검사할 일이 없는 경우
- 상위 클래스에서 재정의한 equals가 하위 클래스에도 맞는 경우
-
클래스가 private, package-private이고 equals메서드를 호출할 일이 없는 경우
-
equals를 호출될 일이 전혀 없는 경우 다음과 같이 막을 수도 있다.
@Override public boolean equals(Object o) { throw new AssertionError(); }
-
동치관계를 만족시키기 위한 다섯가지 요건
반사성(reflexivity)
- 객체는 자기 자신과 같아야 한다는 뜻
- null이 아닌 모든 참조 값 x에 대해, x.equals(x)는 true이다.
대칭성(symmetry)
- 객체는 서로의 동치 여부에 대해 똑같이 답해야 한다는 뜻
- null이 아닌 모든 참 조 값 x, y에 대해, x.equals(y)가 true이면 y.equals(x)도 true이다.
추이성(transitivity)
- A==B, B==C이면, A==C이다.
- null이 아닌 모든 참조 값 x, y, z에 대해, x.equals(y)가 true이고 y.equals(z)도 true이면 x.equals(z)도 true이다.
-
구체 클래스를 확장해 새로운 값을 추가하면서 equals 규약을 만족시킬 방법은 존재하지 않는다.
- 단, 추상 클래스의 하위 클래스에서는 equals규약을 지키면서도 값을 추가할 수 있다.
- 그 외에는 하위 클래스에서 값을 추가할 방법은 없지만, 상속대신 컴포지션을 사용하는 방법을 사용할 수 있다.
일관성(consistency)
- 두 객체가 같다면 수정되지 않는 한 영원히 같아야 한다.
- null이 아닌 모든 참조 값 x, y에 대해, x.equals(y)를 반복해서 호출하면 항상 true를 반환하거나 항상 false를 반환한다.
-
클래스가 불변이든 가변이든 equals의 판단에 신뢰할 수 없는 자원이 끼어들게 해서는 안 된다.
- equals는 항시 메모리에 존재하는 객체만을 사용한 결정적 계산만 수행해야한다.
null아님
- 모든 객체가 null과 같지 않아야 한다.
- null이 아닌 모든 참조 값 x에 대해, x.equals(null)은 false이다.
-
해당 검사를 하기 위해
if(object == null) retuen false;
와 같은 코드는 사용하지 말고instanceof
를 사용해 검사하자.@Override public boolean equals(Object o) { if (o instanceof MyType) return false; MyType mt = (MyType) o; ... }
좋은 equals메서드를 구현하는 순서
==
연산자를 사용해 입력이 자기 자신의 참조인지 확인한다.instanceof
로 입력이 올바른 타입인지 확인한다.- 입력을 올바른 타입으로 형변환 한다.
- 입력 객체와 자기 자신의 대응되는 "핵심"필드들이 모두 일치하는지 하나씩 검사한다.
- 모든 필드가 일치하면 true, 하나라도 다르면 false반환
equals를 재정의 할 때 주의사항
float
,double
을 제외한 기본 타입 필드는==
를 통해 비교하고, 참조 타입은equals
메서드로 비교한다.float
,double
는Float.compare(float, float);
와Double.compare(double, double);
로 비교한다. (부동 소수값을 다뤄야하기 때문)- 때로는 null도 정상 값으로 취급하는 참조 타입 필드가 존재하여, 이러한 필드는 정적 메서드인
Object.equals(Object, Object);
로 비교해NullPointException
을 발생시켜 예방방하는 것이 좋다. - 비교하기 아주 복잡한 필드를 가진 클래스들으니 필드의 표준형을 저장해둔 후 표준형끼리 비교하면 경제적이다.
- 최상의 성능을 바란다면 다를 가능성이 크거나 비교하는 비용이 싼 필드를 먼저 비교하는 것이 좋다. (동기화용 lock 필드같이 객체의 논리적 상태와 관련 없는 필드는 비교하면 안된다.)
- equals를 다 구현하였으면 대칭적인지, 추이성은 있는지, 일관적인지 체크를 해봐야 한다.
- equals를 재정의 할 때 hashCode도 반드시 재정의해야 한다.
- 필드들의 동치성만 확인해도 equals 규약을 어렵지 않게 지킬 수 있음으로 너무 복잡하게 해결하려 들지 않는 것이 좋다.
- Object 이외의 타입을 매개변수로 받는 equals메서드는 재정의가 아니라 다중정의가 되는 것임으로 선언하지 말자!
핵심 정리
많은 경우에 Object의 equals가 우리가 원하는 비교를 정확히 수행해주지만 꼭 필요한 경우가 아니면 equals를 재정의하지 말자. 재정의해야 할 때는 해당 클래스의 핵심 필드를 빠짐없이, 다섯 가지 규약을 확실히 지켜가며 비교해야 한다.