다음과 같이 equals()를 작성해서 Object.equals()를 오버라이딩 했다고 해보자.
public boolean equals(Object obj) {
if (obj == null || ! (obj instanceof SomeClass)) return false;
SomeClass that = (SomeClass)obj;
return ((this.fieldA== null && that.getFieldA() == null) || this.fieldA.equals(that.getFieldA()))
&& ((this.fieldB == null && that.getFieldB() == null) || this.fieldB .equals(that.getFieldB()));
}
이 코드가 과연 어떤 문제가 있을까? 실제로 이런 코드에 단위 테스트가 없다면 쉽게 버그를 찾기 힘들다. 자바 기초를 배우는 때라면 오히려 쉬울지 몰라도, 수천 개 혹은 수만 개 클래스에 속한 메소드라면 더욱 어렵다.
아래 테스트 코드로 문제를 찾아낼 수 있었다.
// someObject 는 정상적으로 초기화
public void testEqualsObject() {
try{
new SomeClass (null, null, null, null, null).equals(someObject);
}catch (NullPointerException e) {
fail(e.getClass().getName() + " is caught");
}
}
또한, 위 코드를 작성했을 때, hashCode() 는 빼먹었다고 하면
hashCode() 의 contract를 위반한다. 더구나 hashCode()를 효율적으로 작성하려면 알고리즘도 고민해야 한다. 고맙게도 이클립스는 알고리즘까지 포함하여 일반화 한 구현을 제공한다. 과거에는 별도 플러그인이 필요했던 것 같은데, 현재는 JDT 안에 기본으로 내장하고 있다.
Source 메뉴(
Alt+Shift+S)에서
Generate hashCode() and equals()... 를 찾을 수 있다. 그러나, 이클립스가 은총알(silver bullet)은 아니기 때문에 equals() 에 사용할 필드는 지정해줘야 한다. 또한, 단순 비교가 아닌 방법으로 구현할 문제라면, 생성한 코드를 수정하는 것은 좋은 습관일 듯 하다. 아래는 사례로 올린 화면이라 속성 명은 좀 다르다.
결론적으로 일반적인 환경이라면 equals/hashCode()를 직접 구현하기 보다는 이클립스로 자동생성해서 혹시라도 발생할 수 있는, 그러면서 오류가 잘 드러나지 않는 버그를 만들지 말자.