Java HashSet 사용법

HashSet은 Set 인터페이스를 구현한 대표적인 컬렉션이다. java.util.HashSet 클래스로 제공되며, 내부적으로 HashMap을 사용해 원소를 관리한다.

HashSet의 핵심 특징

  • 같은 원소의 중복 저장을 허용하지 않는다.
  • 원소의 저장 순서를 보장하지 않는다.
  • 인덱스가 없으므로 get() 메서드를 제공하지 않는다.
  • 원소를 추가할 때는 add(), 포함 여부를 확인할 때는 contains()를 사용한다.

HashSet 생성

Set<Integer> set1 = new HashSet<>();        // 빈 HashSet 생성
Set<Integer> set2 = new HashSet<>(set1);    // set1의 원소를 복사
Set<Integer> set3 = new HashSet<>(10);      // 초기 용량 지정

생성자에 전달하는 숫자는 저장 가능한 최대 원소 수가 아니라 초기 용량이다.

값 추가

Set<Integer> set = new HashSet<>();
 
set.add(1);
set.add(2);
set.add(3);
set.add(1);

HashSet은 중복을 허용하지 않으므로 같은 값을 다시 추가해도 하나만 유지된다.

add()는 새로운 값이 추가되면 true, 이미 존재하는 값이면 false를 반환한다.

boolean added = set.add(1);

값 삭제

Set<Integer> set = new HashSet<>();
 
set.add(1);
set.add(2);
set.add(3);
 
set.remove(1); // 값 1 제거
set.clear();   // 모든 값 제거

remove(value)는 지정한 값을 제거하고, clear()는 저장된 모든 값을 제거한다.

clear()는 컬렉션 객체를 계속 사용하면서 원소만 비울 때 사용한다. 사용이 끝난 모든 HashSet에 반드시 호출해야 하는 메서드는 아니다.

순회와 출력

Set<Integer> set = new HashSet<>();
 
set.add(1);
set.add(2);
set.add(3);
 
System.out.println(set);

HashSet을 출력하면 원소가 대괄호로 묶여 표시되지만 출력 순서는 보장되지 않는다.

반복자를 사용해 순회할 수 있다.

Iterator<Integer> iterator = set.iterator();
 
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}

향상된 for 문을 사용할 수도 있다.

for (int number : set) {
    System.out.println(number);
}

값 검색

Set<Integer> set = new HashSet<>();
 
set.add(1);
set.add(2);
set.add(3);
 
System.out.println(set.contains(1)); // true
System.out.println(set.contains(5)); // false

contains(value)는 지정한 값이 존재하면 true, 존재하지 않으면 false를 반환한다.

시간복잡도

연산평균 시간복잡도
add()O(1)
contains()O(1)
remove()O(1)
전체 순회O(N + capacity)

평균 시간복잡도는 해시값이 고르게 분산된다는 전제다. 해시 충돌이 집중되면 조회·추가·삭제 성능이 O(N)까지 저하될 수 있다. 전체 순회 비용은 저장된 원소 수 N뿐 아니라 내부 버킷의 용량에도 영향을 받는다.

사용자 정의 객체를 저장할 때

HashSet은 hashCode()와 equals()를 사용해 중복 여부를 판단한다. 사용자 정의 객체를 저장할 때 두 메서드를 일관되게 구현하지 않으면 논리적으로 같은 객체도 서로 다른 원소로 저장될 수 있다.

또한 원소를 저장한 뒤 동등성 판단에 사용되는 필드를 변경하면 해당 원소를 contains()나 remove()로 찾지 못할 수 있다. 가능하면 불변 객체를 원소로 사용한다.

집합 연산

addAll(), retainAll(), removeAll()을 사용해 합집합, 교집합, 차집합을 구할 수 있다. 원본을 보존하려면 새로운 HashSet에 복사한 뒤 연산한다.

Set<Integer> a = new HashSet<>(Arrays.asList(1, 2, 3));
Set<Integer> b = new HashSet<>(Arrays.asList(3, 4, 5));
 
Set<Integer> union = new HashSet<>(a);
union.addAll(b); // 합집합: [1, 2, 3, 4, 5]
 
Set<Integer> intersection = new HashSet<>(a);
intersection.retainAll(b); // 교집합: [3]
 
Set<Integer> difference = new HashSet<>(a);
difference.removeAll(b); // 차집합: [1, 2]

결과의 출력 순서는 보장되지 않는다.