HashSet은 Set 인터페이스를 구현한 클래스로, 중복 값을 허용하지 않습니다. 또한 HashSet은 동기화되지 않았으며(not synchronized), 스레드에 안전하지 않습니다(not thread-safe). HashSet에 이미 존재하는 요소를 다시 추가하려고 하면 add() 메서드가 false를 반환하며, 해당 중복 요소는 저장되지 않습니다.
HashSet의 선언 구문
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, Serializable
커스텀 HashSet 구현하기
아래 예제에서는 내부적으로 HashMap을 활용하여 커스텀 HashSet(Custom HashSet)을 직접 구현하는 방법을 살펴봅니다. HashMap의 키(key)는 중복을 허용하지 않는다는 특성을 이용하면 Set의 동작을 손쉽게 재현할 수 있습니다. 실제로 자바 표준 라이브러리의 HashSet도 내부적으로 HashMap을 사용하여 구현되어 있습니다.
예제 코드
import java.util.*;
public class CustomHashSetTest extends AbstractSet<Object> {
private transient HashMap<Object, Object> map;
// 더미 값으로 사용할 상수 객체
private static final Object PRESENT = new Object();
public CustomHashSetTest() {
map = new HashMap<>();
}
@Override
public boolean add(Object e) {
// put()이 null을 반환하면 새로운 키(중복 아님)
return map.put(e, PRESENT) == null;
}
@Override
public Iterator<Object> iterator() {
return map.keySet().iterator();
}
@Override
public int size() {
return map.size();
}
public static void main(String[] args) {
CustomHashSetTest set = new CustomHashSetTest();
set.add("India");
set.add("Australia");
set.add("England");
set.add("India"); // 중복 요소 → 추가되지 않음
for (Object obj : set) {
System.out.println(obj);
}
}
}
출력 결과
England Australia India
출력 결과에서 확인할 수 있듯이 "India"를 두 번 추가했지만 실제로는 한 번만 저장되었습니다. 그 이유는 add() 메서드가 내부적으로 HashMap의 put() 메서드를 호출하는데, 이미 존재하는 키에 대해서는 기존 값(PRESENT)을 반환하기 때문에 null과 비교한 결과가 false가 되어 중복 추가가 차단됩니다.
이처럼 HashMap의 키 중복 불가 특성과 AbstractSet 클래스를 상속하는 방식을 조합하면, 자바에서 간단하면서도 효율적인 커스텀 HashSet을 구현할 수 있습니다.