
알고리즘을 풀며 정렬문제를 풀다보면 hash 값을 사용하는 자료구조 문제에서 equals와 hashCode 메소드를 재정의 한 후 문제에 접근해야하는 경우가 더러 존재했다.
메소드를 재정의하는 경우는 알지만 왜 두개를 같이 재정의 해야할까?
hash 값을 사용하는 자료구조는 HashSet이나 HashMap 등이 있다.
이 자료구조들은 값을 저장할때, hash값 생성을 통해 값을 비교하고, 동일한 값은 중복 저장하지 않는다.
이 경우 hashCode의 메소드 반환값을 사용하게 되는데, hashCode의 반환 값이 일치하고 이후 equals 메소드의 반환값이 true 일때만 논리적으로 같은 객체라고 판단하게 된다.
class EqualsHashCodeTest {
@Test
@DisplayName("equals만 정의하면 HashSet이 제대로 동작하지 않는다.")
void test() {
// 아래 2개는 같은 구독자
Subscribe subscribe1 = new Subscribe("team.maeilmail@gmail.com", "backend");
Subscribe subscribe2 = new Subscribe("team.maeilmail@gmail.com", "backend");
HashSet<Subscribe> subscribes = new HashSet<>(List.of(subscribe1, subscribe2));
// 결과는 1개여야하는데..? 2개가 나온다.
System.out.println(subscribes.size());
}
class Subscribe {
private final String email;
private final String category;
public Subscribe(String email, String category) {
this.email = email;
this.category = category;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Subscribe subscribe = (Subscribe) o;
return Objects.equals(email, subscribe.email) && Objects.equals(category, subscribe.category);
}
}
}
예제 코드의 HashSet인 subscribes의 선언부를 보자.
Subscribe subscribe1 = new Subscribe("team.maeilmail@gmail.com", "backend");
Subscribe subscribe2 = new Subscribe("team.maeilmail@gmail.com", "backend");
우리가 보기에 subscribe1 과 subscribe2는 같은 email 값과 같은 category 값을 가지고 생성되었기에 같은 값이라고 생각되고
HashSet<Subscribe> subscribes = new HashSet<>(List.of(subscribe1, subscribe2));
위와 같이 hashset에 넣게되면 중복된 값이 제거되어 System.out.println(subscribes.size());를 수행했을때, 1이 나온다고 예상할것이다.
하지만 출력결과 2라는 값이 나온다.
답은 Subscribe 클래스 내부에 함수 재정의 부분을 보면 알 수 있다.
equals 메소드는 override 를 통해 재정의를 잘 했지만 hashCode 메소드의 재정의 부분은 찾을 수 없다.
결과적으로 Object에서 사용되는 기본 hashCode 메소드를 사용하게 되고 이 기본 hashCode 메소드는 객체의 고유 주소로 같다 아니다를 판별하기에 객체마다 다른 값을 반환하여 다른객체로 판단된다. + equals도 주소를 기준으로 같다 아니다를 판별한다.
이러한 이유로 HashSet에서 다른 객체로 판단, 중복처리가 되지 않는다.
그렇다면 hashCode와 equals를 매번 동시에 재정의하게되는것에서 귀찮음을 느낄 수 있다...
@Lombok을 사용할 수 있는 환경에서는 대안이 존재한다 !
평소에도 getter, setter 등으로 수고를 덜어주는 롬복에서는 @EqualsAndHashCode 라는 어노테이션이 존재한다.
import lombok.EqualsAndHashCode;
@EqualsAndHashCode
public class Subscribe {
private String email;
private String category;
}
이런식으로 어노테이션을 사용하게되면 equals와 hashCode 메소드를 자동으로 생성하고 코드도 더 짧게 사용할 수 있다 !
면접 질문 내용과 답변의 일부는 기술 면접 구독 서비스 - 매일메일 에 있다.
흥미로웠다면 구독해보는 것도 추천한다!