[Java 개념노트 49] Set과 HashSet 이해하기
안녕하세요. Java 개념노트 시리즈 마흔아홉 번째 글입니다.
지난 글에서는 순서가 있는 데이터 목록을 다루는 List와 ArrayList에 대해 정리했습니다. 이번 글에서는 중복을 허용하지 않는 데이터 저장 구조인 Set과 HashSet에 대해 알아보겠습니다.
List는 같은 값을 여러 번 저장할 수 있었습니다. 하지만 어떤 상황에서는 중복된 값을 자동으로 걸러내고, 하나의 값만 저장하고 싶을 때가 있습니다. 이럴 때 사용할 수 있는 컬렉션이 바로 Set입니다.
1. Set이란?
Set은 중복을 허용하지 않는 컬렉션입니다. 같은 값을 여러 번 추가해도 하나만 저장됩니다.
쉽게 말하면Set은 같은 값이 여러 번 들어오면 중복을 제거하고 하나만 보관하는 데이터 저장 구조입니다.
Set 특징
1. 중복 값을 허용하지 않는다.
2. 값의 존재 여부를 확인하기 좋다.
3. 인덱스로 접근하지 않는다.
4. HashSet은 저장 순서를 보장하지 않을 수 있다.
2. Set을 사용하는 상황
Set은 중복을 제거하거나, 어떤 값이 이미 존재하는지 빠르게 확인하고 싶을 때 자주 사용합니다.
- 중복 없는 태그 목록을 만들 때
- 이미 사용 중인 아이디를 확인할 때
- 방문한 페이지나 항목을 저장할 때
- 중복 상품 코드나 중복 번호를 제거할 때
- 여러 값 중 고유한 값만 남기고 싶을 때
예를 들어 사용자가 같은 태그를 여러 번 입력해도 화면에는 한 번만 보여주고 싶다면 Set을 사용할 수 있습니다.
3. HashSet이란?
HashSet은 Set을 구현한 대표적인 클래스입니다. 중복을 허용하지 않고, 값을 빠르게 저장하고 찾기 위해 해시 구조를 사용합니다.
Set → 중복 없는 값 저장 규칙
HashSet → Set 규칙을 실제로 구현한 대표 클래스
입문 단계에서는 Set을 배울 때 가장 먼저 HashSet을 익히면 좋습니다.
4. HashSet import하기
HashSet은 java.util 패키지에 있습니다. 사용하려면 파일 위쪽에 import를 작성해야 합니다.
import java.util.HashSet;
Set 타입으로 선언하고 싶다면 Set도 함께 import합니다.
import java.util.Set;
import java.util.HashSet;
5. HashSet 만들기
문자열을 저장하는 HashSet은 다음과 같이 만들 수 있습니다.
HashSet<String> set = new HashSet<>();
<String>은 이 HashSet에 문자열만 저장하겠다는 뜻입니다.
Set 타입으로 선언할 수도 있습니다.
Set<String> set = new HashSet<>();
| 코드 | 의미 |
Set<String> |
문자열을 저장하는 Set 타입 |
set |
Set을 가리키는 변수 이름 |
new HashSet<>() |
실제 HashSet 객체 생성 |
6. add()로 값 추가하기
add()는 HashSet에 값을 추가할 때 사용합니다.
import java.util.HashSet;
public class Main {
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
set.add("Java");
set.add("Spring");
set.add("SQL");
System.out.println(set);
}
}
실행 결과는 다음과 비슷합니다.
[Java, Spring, SQL]
HashSet은 저장 순서를 보장하지 않을 수 있습니다. 따라서 출력 순서는 실행 환경에 따라 다르게 보일 수 있습니다.
주의HashSet은 중복 제거가 핵심입니다. 저장 순서가 반드시 필요하다면 List나 LinkedHashSet을 고려해야 합니다.
7. Set은 중복을 허용하지 않는다
HashSet에 같은 값을 여러 번 추가해보겠습니다.
HashSet<String> set = new HashSet<>();
set.add("Java");
set.add("Spring");
set.add("Java");
set.add("SQL");
set.add("Spring");
System.out.println(set);
System.out.println(set.size());
실행 결과는 다음과 비슷합니다.
[Java, Spring, SQL]
3
"Java"와 "Spring"을 여러 번 추가했지만 각각 하나씩만 저장되었습니다.
8. add()의 반환값
HashSet의 add()는 값을 추가했는지 여부를 boolean으로 반환합니다.
- 새로운 값이 추가되면
true - 이미 있는 값이라 추가되지 않으면
false
HashSet<String> set = new HashSet<>();
System.out.println(set.add("Java"));
System.out.println(set.add("Spring"));
System.out.println(set.add("Java"));
System.out.println(set);
실행 결과는 다음과 비슷합니다.
true
true
false
[Java, Spring]
세 번째 set.add("Java")는 이미 존재하는 값이기 때문에 추가되지 않고 false를 반환합니다.
9. contains()로 포함 여부 확인하기
contains()는 특정 값이 Set 안에 있는지 확인할 때 사용합니다.
HashSet<String> set = new HashSet<>();
set.add("Java");
set.add("Spring");
set.add("SQL");
System.out.println(set.contains("Java"));
System.out.println(set.contains("Oracle"));
실행 결과는 다음과 같습니다.
true
false
"Java"는 Set 안에 있으므로 true이고, "Oracle"은 없으므로 false입니다.
10. remove()로 값 삭제하기
remove()는 Set에서 특정 값을 삭제할 때 사용합니다.
HashSet<String> set = new HashSet<>();
set.add("Java");
set.add("Spring");
set.add("SQL");
set.remove("SQL");
System.out.println(set);
실행 결과는 다음과 비슷합니다.
[Java, Spring]
"SQL" 값이 삭제되었습니다.
11. remove()의 반환값
remove()도 삭제 성공 여부를 boolean으로 반환합니다.
HashSet<String> set = new HashSet<>();
set.add("Java");
set.add("Spring");
System.out.println(set.remove("Java"));
System.out.println(set.remove("Oracle"));
System.out.println(set);
실행 결과는 다음과 비슷합니다.
true
false
[Spring]
"Java"는 존재했으므로 삭제되고 true를 반환합니다. "Oracle"은 없었기 때문에 삭제할 값이 없어 false를 반환합니다.
12. size(), isEmpty(), clear()
HashSet에서도 저장된 개수 확인, 비어 있는지 확인, 전체 삭제를 할 수 있습니다.
HashSet<String> set = new HashSet<>();
System.out.println(set.isEmpty());
set.add("Java");
set.add("Spring");
System.out.println(set.size());
System.out.println(set.isEmpty());
set.clear();
System.out.println(set);
System.out.println(set.isEmpty());
실행 결과는 다음과 같습니다.
true
2
false
[]
true
| 메서드 | 역할 |
size() |
저장된 값의 개수를 반환합니다. |
isEmpty() |
비어 있는지 확인합니다. |
clear() |
모든 값을 삭제합니다. |
13. HashSet은 인덱스로 접근하지 않는다
List는 인덱스가 있어서 get(0)처럼 값을 꺼낼 수 있었습니다. 하지만 HashSet은 인덱스로 값을 꺼내지 않습니다.
HashSet<String> set = new HashSet<>();
set.add("Java");
set.add("Spring");
// set.get(0); // 오류 발생
HashSet에는 get(index) 메서드가 없습니다. Set은 특정 위치의 값을 꺼내기보다, 값이 존재하는지 확인하거나 전체를 반복하는 방식으로 사용합니다.
기억하기
인덱스로 접근해야 한다면 List가 적합하고, 중복 없는 값 저장이 목적이라면 Set이 적합합니다.
14. 향상된 for문으로 반복하기
HashSet은 인덱스가 없기 때문에 보통 향상된 for문으로 반복합니다.
HashSet<String> set = new HashSet<>();
set.add("Java");
set.add("Spring");
set.add("SQL");
for (String item : set) {
System.out.println(item);
}
실행 결과는 다음과 비슷합니다.
Java
Spring
SQL
출력 순서는 고정되어 있다고 생각하면 안 됩니다. HashSet은 순서를 보장하지 않을 수 있습니다.
15. List에서 Set으로 중복 제거하기
List에 중복된 값이 있을 때 Set으로 변환하면 중복을 제거할 수 있습니다.
import java.util.ArrayList;
import java.util.HashSet;
public class Main {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("Java");
list.add("Spring");
list.add("Java");
list.add("SQL");
list.add("Spring");
HashSet<String> set = new HashSet<>(list);
System.out.println("List: " + list);
System.out.println("Set: " + set);
}
}
실행 결과는 다음과 비슷합니다.
List: [Java, Spring, Java, SQL, Spring]
Set: [Java, Spring, SQL]
List에는 중복이 그대로 있지만, Set으로 바꾸면 중복이 제거됩니다.
16. Set에서 List로 다시 변환하기
Set의 값을 다시 List로 바꾸고 싶다면 new ArrayList<>(set) 형태로 만들 수 있습니다.
import java.util.ArrayList;
import java.util.HashSet;
public class Main {
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
set.add("Java");
set.add("Spring");
set.add("SQL");
ArrayList<String> list = new ArrayList<>(set);
System.out.println(list);
}
}
실행 결과는 다음과 비슷합니다.
[Java, Spring, SQL]
다만 Set에서 List로 바꿔도 HashSet의 순서가 보장되는 것은 아닙니다. 순서가 중요하다면 처음부터 순서가 보장되는 구조를 선택해야 합니다.
17. HashSet에 Integer 저장하기
HashSet에는 숫자도 저장할 수 있습니다. 단, 컬렉션 제네릭에는 기본 자료형을 직접 사용할 수 없으므로 Integer를 사용합니다.
import java.util.HashSet;
public class Main {
public static void main(String[] args) {
HashSet<Integer> numbers = new HashSet<>();
numbers.add(10);
numbers.add(20);
numbers.add(10);
numbers.add(30);
System.out.println(numbers);
System.out.println(numbers.size());
}
}
실행 결과는 다음과 비슷합니다.
[20, 10, 30]
3
10을 두 번 추가했지만 하나만 저장되었습니다.
18. HashSet에 객체 저장하기
HashSet에는 직접 만든 객체도 저장할 수 있습니다.
import java.util.HashSet;
public class Main {
public static void main(String[] args) {
HashSet<Member> members = new HashSet<>();
members.add(new Member("user01"));
members.add(new Member("user01"));
System.out.println(members.size());
}
}
class Member {
String id;
Member(String id) {
this.id = id;
}
}
실행 결과는 다음과 같을 수 있습니다.
2
두 객체의 id는 같지만, 서로 다른 객체입니다. 기본 상태에서는 HashSet이 두 객체를 다른 값으로 판단할 수 있습니다.
19. 객체 중복 판단에는 equals()와 hashCode()가 중요하다
HashSet이 객체의 중복을 판단할 때는 equals()와 hashCode()가 중요합니다.
예를 들어 회원 id가 같으면 같은 회원으로 보고 싶다면 equals()와 hashCode()를 오버라이딩해야 합니다.
import java.util.HashSet;
public class Main {
public static void main(String[] args) {
HashSet<Member> members = new HashSet<>();
members.add(new Member("user01"));
members.add(new Member("user01"));
System.out.println(members.size());
}
}
class Member {
String id;
Member(String id) {
this.id = id;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Member)) {
return false;
}
Member other = (Member) obj;
return this.id.equals(other.id);
}
@Override
public int hashCode() {
return id.hashCode();
}
}
실행 결과는 다음과 같습니다.
1
두 객체의 id가 같으면 같은 회원으로 판단하도록 만들었기 때문에 하나만 저장됩니다.
핵심 포인트
HashSet에 직접 만든 객체를 저장할 때 중복 판단 기준을 바꾸고 싶다면 equals()와 hashCode()를 함께 오버라이딩해야 합니다.
20. HashSet, LinkedHashSet, TreeSet 맛보기
Set 계열에는 HashSet 외에도 여러 구현 클래스가 있습니다. 입문 단계에서는 이름과 특징만 가볍게 알아두면 됩니다.
| 구현 클래스 | 특징 |
HashSet |
중복을 허용하지 않고, 일반적으로 순서를 보장하지 않습니다. |
LinkedHashSet |
중복을 허용하지 않고, 입력 순서를 유지합니다. |
TreeSet |
중복을 허용하지 않고, 정렬된 순서로 저장합니다. |
이번 글에서는 가장 기본이 되는 HashSet을 중심으로 이해하면 충분합니다.
21. HashSet 전체 예제
이번에는 HashSet의 주요 기능을 한 번에 사용하는 예제를 보겠습니다.
import java.util.HashSet;
public class Main {
public static void main(String[] args) {
HashSet<String> tags = new HashSet<>();
tags.add("Java");
tags.add("Spring");
tags.add("SQL");
tags.add("Java");
System.out.println("태그 목록: " + tags);
System.out.println("태그 개수: " + tags.size());
System.out.println("Java 포함 여부: " + tags.contains("Java"));
System.out.println("Oracle 포함 여부: " + tags.contains("Oracle"));
boolean removed = tags.remove("SQL");
System.out.println("SQL 삭제 성공 여부: " + removed);
for (String tag : tags) {
System.out.println("태그: " + tag);
}
tags.clear();
System.out.println("전체 삭제 후: " + tags);
System.out.println("비어 있는가? " + tags.isEmpty());
}
}
실행 결과는 다음과 비슷합니다.
태그 목록: [Java, Spring, SQL]
태그 개수: 3
Java 포함 여부: true
Oracle 포함 여부: false
SQL 삭제 성공 여부: true
태그: Java
태그: Spring
전체 삭제 후: []
비어 있는가? true
출력 순서는 실행 환경에 따라 다르게 보일 수 있습니다. 중요한 점은 중복된 값이 하나만 저장된다는 것입니다.
22. Set과 HashSet 사용 시 자주 하는 실수
Set과 HashSet을 처음 사용할 때는 아래와 같은 실수를 자주 합니다.
| 실수 | 문제점 | 해결 방법 |
| import 누락 | HashSet 클래스를 찾지 못함 | import java.util.HashSet; 작성 |
| HashSet에서 순서를 기대함 | 출력 순서가 달라질 수 있음 | 순서가 필요하면 List 또는 LinkedHashSet 고려 |
Set에서 get(0) 사용 시도 |
Set은 인덱스로 접근하지 않음 | 반복문으로 순회하거나 List 사용 |
| 중복값이 왜 저장되지 않는지 헷갈림 | Set은 중복을 허용하지 않음 | 중복 저장이 필요하면 List 사용 |
| 객체 중복 판단 기준을 설정하지 않음 | id가 같아도 다른 객체로 저장될 수 있음 | equals(), hashCode() 오버라이딩 |
23. 직접 연습해보기
아래 코드를 직접 작성하고 실행해보세요.
import java.util.ArrayList;
import java.util.HashSet;
public class Main {
public static void main(String[] args) {
ArrayList<String> searchWords = new ArrayList<>();
searchWords.add("Java");
searchWords.add("Spring");
searchWords.add("Java");
searchWords.add("SQL");
searchWords.add("Spring");
searchWords.add("Oracle");
System.out.println("검색어 전체: " + searchWords);
System.out.println("검색어 전체 개수: " + searchWords.size());
HashSet<String> uniqueWords = new HashSet<>(searchWords);
System.out.println("중복 제거 검색어: " + uniqueWords);
System.out.println("중복 제거 후 개수: " + uniqueWords.size());
System.out.println("Java 포함 여부: " + uniqueWords.contains("Java"));
uniqueWords.remove("SQL");
System.out.println("SQL 삭제 후: " + uniqueWords);
}
}
실행 결과는 다음과 비슷합니다.
검색어 전체: [Java, Spring, Java, SQL, Spring, Oracle]
검색어 전체 개수: 6
중복 제거 검색어: [Java, Spring, SQL, Oracle]
중복 제거 후 개수: 4
Java 포함 여부: true
SQL 삭제 후: [Java, Spring, Oracle]
HashSet의 출력 순서는 다르게 보일 수 있습니다. 하지만 중복 제거 결과는 동일하게 이해하면 됩니다.
24. 이번 글 정리
이번 글에서는 중복을 허용하지 않는 컬렉션인 Set과 HashSet에 대해 정리했습니다. 핵심 내용은 다음과 같습니다.
Set은 중복을 허용하지 않는 컬렉션이다.HashSet은 Set의 대표적인 구현 클래스이다.HashSet은 저장 순서를 보장하지 않을 수 있다.add()는 값을 추가하고, 추가 성공 여부를 boolean으로 반환한다.- 이미 있는 값을 다시 추가하면 저장되지 않고
false를 반환한다. contains()는 값 포함 여부를 확인한다.remove()는 값을 삭제하고, 삭제 성공 여부를 boolean으로 반환한다.size()는 저장된 값의 개수를 반환한다.isEmpty()는 비어 있는지 확인한다.clear()는 모든 값을 삭제한다.- HashSet은 인덱스로 접근하지 않으므로
get()을 사용할 수 없다. - List를 HashSet으로 바꾸면 중복 제거에 활용할 수 있다.
- 직접 만든 객체의 중복 판단에는
equals()와hashCode()가 중요하다.
한 줄 요약HashSet은 중복을 허용하지 않는 값을 저장할 때 사용하는 Set 구현 클래스이며, 순서보다 고유한 값 관리가 중요할 때 사용합니다.
다음 글 예고
다음 글에서는 [Java 개념노트 50] Map과 HashMap 이해하기라는 주제로 key와 value를 한 쌍으로 저장하고 key로 값을 찾는 구조를 자세히 정리해보겠습니다.
GWDEVELBlog Java 개념노트 시리즈

'Computer Science > Java' 카테고리의 다른 글
| [Java] 개념노트 51 Iterator와 컬렉션 반복 이해하기 (0) | 2026.06.17 |
|---|---|
| [Java ] 개념노트 50 Map과 HashMap 이해하기 (0) | 2026.06.17 |
| [Java ] 개념노트 48 List와 ArrayList 이해하기 (0) | 2026.06.16 |
| [Java ] 개념노트 47 컬렉션 프레임워크란 무엇인가? (0) | 2026.06.16 |
| [Java ] 개념노트 46 사용자 정의 예외 이해하기 (0) | 2026.06.16 |