1. Set이란 무엇인가?
Java 프로그래밍에서 Set은 가장 중요한 컬렉션 타입 중 하나입니다. “Set”이라는 단어는 수학에서 유래되었으며, 수학적 집합과 마찬가지로 중복 요소를 포함할 수 없음이라는 핵심 특성을 가집니다.
Set은 숫자, 문자열, 객체와 같은 데이터 타입에 관계없이 고유한 값만 관리하고자 할 때 사용됩니다.
Set과 List의 차이점은 무엇인가?
Java Collections Framework는 List와 Map과 같은 여러 데이터 구조를 제공합니다. 그중 Set과 List는 자주 비교됩니다. 주요 차이점은 다음과 같습니다:
- List : 중복 값을 허용하며 요소 순서(인덱스 기반)를 보존합니다.
- Set : 중복을 허용하지 않으며, 요소 순서가 보장되지 않습니다(특정 구현을 제외하고).
요약하자면, List는 “순서가 있는 컬렉션”인 반면 Set은 “고유한 요소의 컬렉션”입니다.
예를 들어, 중복 없이 사용자 ID를 관리하고자 할 때 Set이 이상적인 선택입니다.
Set을 사용하는 장점
- 자동 중복 제거 사용자들로부터 대량의 데이터를 받더라도 Set에 요소를 단순히 추가하기만 하면 중복이 한 번만 저장됩니다. 이는 수동 중복 검사를 필요로 하지 않으며 구현을 단순화합니다.
- 효율적인 검색 및 제거 Set은 존재 확인과 제거 작업을 빠르게 수행하도록 설계되었으며, 성능은 구현(HashSet 또는 TreeSet 등)에 따라 다릅니다.
Set을 언제 사용해야 하는가?
- 사용자 이메일 주소나 ID와 같이 중복되지 않아야 하는 정보를 관리할 때
- 데이터 고유성을 보장해야 할 때
- 대규모 데이터셋에서 고유한 값의 목록을 효율적으로 생성하고자 할 때
위에서 보여지듯이, Set은 중복을 허용하지 않는 컬렉션을 스마트하게 처리하기 위한 Java의 표준 메커니즘입니다.
다음 섹션에서는 Set의 사양, 사용 패턴, 그리고 구체적인 코드 예제를 자세히 탐구하겠습니다.
2. Set의 기본 사양과 이점
Java에서 Set은 java.util.Set 인터페이스로 정의됩니다. 이 인터페이스를 구현함으로써 중복이 없는 고유한 요소의 컬렉션을 표현할 수 있습니다. Set의 핵심 사양과 장점을 자세히 살펴보겠습니다.
Set 인터페이스의 기본 특성
Set은 다음과 같은 특성을 가집니다:
- 중복 요소 없음 이미 존재하는 요소를 추가하려고 하면 추가되지 않습니다. 예를 들어,
set.add("apple")을 두 번 실행하더라도 “apple”은 하나만 저장됩니다. - 순서 보장되지 않음(구현에 따라 다름) Set은 기본적으로 요소 순서를 보장하지 않습니다. 그러나
LinkedHashSet이나TreeSet과 같은 특정 구현은 요소를 특정 순서로 관리합니다. - null 요소 처리 null이 허용되는지는 구현에 따라 다릅니다. 예를 들어,
HashSet은 하나의 null 요소를 허용하지만TreeSet은 허용하지 않습니다.
equals와 hashCode의 중요성
Set에서 두 요소가 중복으로 간주되는지는 equals와 hashCode 메서드에 의해 결정됩니다.
Set 요소로 사용자 정의 클래스를 사용할 때 이 메서드들을 적절히 오버라이드하지 않으면 예상치 못한 중복이나 잘못된 저장 동작이 발생할 수 있습니다.
equals: 두 객체가 논리적으로 같은지 판단합니다hashCode: 효율적인 식별을 위해 사용되는 숫자 값을 반환합니다
Set을 사용하는 이점
Set은 여러 실용적인 장점을 제공합니다:
- 쉬운 중복 제거 Set에 값을 단순히 추가하기만 하면 중복이 자동으로 제거되므로 수동 검사가 필요 없습니다.
- 효율적인 검색 및 제거
HashSet과 같은 구현은 빠른 조회와 제거 작업을 제공하며, 종종 List보다 우수한 성능을 발휘합니다. - 간단하고 직관적인 API
add,remove,contains와 같은 기본 메서드가 Set을 쉽게 사용할 수 있게 합니다.
내부 구현과 성능
One of the most common Set implementations, HashSet, internally uses a HashMap to manage elements. This allows element addition, removal, and lookup to be performed with average O(1) time complexity.
If ordering or sorting is required, you can choose implementations such as LinkedHashSet or TreeSet depending on your needs.
3. 주요 구현 클래스와 그 특성
Java는 Set 인터페이스의 여러 주요 구현을 제공합니다. 각각은 서로 다른 특성을 가지고 있으므로 사용 사례에 맞는 적절한 구현을 선택하는 것이 중요합니다.
여기서는 가장 많이 사용되는 세 가지 구현인 HashSet, LinkedHashSet, TreeSet에 대해 설명합니다.
HashSet
HashSet은 가장 일반적으로 사용되는 Set 구현입니다.
- 특징
- 요소 순서를 보존하지 않음 (삽입 순서와 반복 순서가 다를 수 있습니다).
- 내부적으로
HashMap을 사용하여 빠른 추가, 검색 및 제거 연산을 제공합니다. - 하나의
null요소를 허용합니다. - 전형적인 사용 사례
- 중복을 제거하고 순서가 중요하지 않을 때 이상적입니다.
- 샘플 코드
Set<String> set = new HashSet<>(); set.add("apple"); set.add("banana"); set.add("apple"); // Duplicate is ignored for (String s : set) { System.out.println(s); // Only "apple" and "banana" are printed }
LinkedHashSet
LinkedHashSet는 삽입 순서를 보존함으로써 HashSet의 기능을 확장합니다.
- 특징
- 요소가 삽입된 순서대로 반복됩니다.
- 해시 테이블과 연결 리스트의 조합으로 내부적으로 관리됩니다.
HashSet보다 약간 느리지만 순서가 중요할 때 유용합니다.- 전형적인 사용 사례
- 삽입 순서를 유지하면서 중복을 제거하고 싶을 때 가장 적합합니다.
- 샘플 코드
Set<String> set = new LinkedHashSet<>(); set.add("apple"); set.add("banana"); set.add("orange"); for (String s : set) { System.out.println(s); // Printed in order: apple, banana, orange }
TreeSet
TreeSet은 요소를 자동으로 정렬하는 Set 구현입니다.
- 특징
- 내부적으로 레드-블랙 트리(균형 트리 구조)를 사용합니다.
- 요소가 자동으로 오름차순으로 정렬됩니다.
Comparable또는Comparator를 사용하여 사용자 정의 정렬이 가능합니다.null값은 허용되지 않습니다.- 전형적인 사용 사례
- 고유성 및 자동 정렬이 모두 필요할 때 유용합니다.
- 샘플 코드
Set<Integer> set = new TreeSet<>(); set.add(30); set.add(10); set.add(20); for (Integer n : set) { System.out.println(n); // Printed in order: 10, 20, 30 }
요약
- HashSet : 순서가 필요 없을 때 높은 성능을 제공
- LinkedHashSet : 삽입 순서가 중요할 때 사용
- TreeSet : 자동 정렬이 필요할 때 사용
올바른 Set 구현을 선택하는 것은 특정 요구 사항에 따라 달라집니다. 가장 적합한 구현을 선택하고 효과적으로 사용하세요.
4. 일반 메서드 및 사용 방법
Set 인터페이스는 컬렉션 작업을 위한 다양한 메서드를 제공합니다. 아래는 가장 일반적으로 사용되는 메서드이며, 예시와 함께 설명합니다.
주요 메서드
add(E e)Set에 요소를 추가합니다. 이미 존재하는 요소라면 추가되지 않습니다.remove(Object o)Set에서 지정된 요소를 제거합니다. 성공하면 true를 반환합니다.contains(Object o)Set이 지정된 요소를 포함하고 있는지 확인합니다.size()Set에 포함된 요소의 개수를 반환합니다.clear()Set의 모든 요소를 제거합니다.isEmpty()Set이 비어 있는지 확인합니다.iterator()요소들을 순회할 수 있는 Iterator를 반환합니다.toArray()Set을 배열로 변환합니다.
기본 사용 예시
Set<String> set = new HashSet<>();
// Add elements
set.add("apple");
set.add("banana");
set.add("apple"); // Duplicate ignored
// Get size
System.out.println(set.size()); // 2
// Check existence
System.out.println(set.contains("banana")); // true
// Remove element
set.remove("banana");
System.out.println(set.contains("banana")); // false
// Clear all elements
set.clear();
System.out.println(set.isEmpty()); // true
Iterating Over a Set
Since Set does not support index-based access (e.g., set.get(0)), use an Iterator or enhanced for-loop.
// Enhanced for-loop
Set<String> set = new HashSet<>();
set.add("A");
set.add("B");
set.add("C");
for (String s : set) {
System.out.println(s);
}
// Using Iterator
Iterator<String> it = set.iterator();
while (it.hasNext()) {
String s = it.next();
System.out.println(s);
}
Important Notes
- Adding an existing element using
adddoes not change the Set. - Element order depends on the implementation (HashSet: unordered, LinkedHashSet: insertion order, TreeSet: sorted).
5. Common Use Cases and Typical Scenarios
Java Sets are widely used in many situations where duplicate values must be avoided. Below are some of the most common and practical use cases encountered in real-world development.
Creating a Unique List (Duplicate Removal)
When you want to extract only unique values from a large dataset, Set is extremely useful.
For example, it can automatically remove duplicates from user input or existing collections.
Example: Creating a Set from a List to Remove Duplicates
List<String> list = Arrays.asList("apple", "banana", "apple", "orange");
Set<String> set = new HashSet<>(list);
System.out.println(set); // [apple, banana, orange]

Ensuring Input Uniqueness
Sets are ideal for scenarios where duplicate values must not be registered, such as user IDs or email addresses.
You can immediately determine whether a value already exists by checking the return value of add.
Set<String> emailSet = new HashSet<>();
boolean added = emailSet.add("user@example.com");
if (!added) {
System.out.println("This value is already registered");
}
Storing Custom Classes and Implementing equals/hashCode
When storing custom objects in a Set, proper implementation of equals and hashCode is essential.
Without them, objects with the same logical content may be treated as different elements.
Example: Ensuring Uniqueness in a Person Class
class Person {
String name;
Person(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
return Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
}
// Example usage
Set<Person> people = new HashSet<>();
people.add(new Person("Taro"));
people.add(new Person("Taro")); // Without proper implementation, duplicates may occur
System.out.println(people.size()); // 1
Fast Lookup and Data Filtering
Because Set provides fast lookups via contains, it is often used for filtering and comparison tasks.
Converting a List to a Set can significantly improve performance when repeatedly checking for existence.
Example: Fast Keyword Lookup
Set<String> keywordSet = new HashSet<>(Arrays.asList("java", "python", "c"));
boolean found = keywordSet.contains("python"); // true
6. Performance Considerations and Pitfalls
While Set is a powerful collection for managing unique elements, improper usage can lead to unexpected behavior or performance issues. This section explains key performance characteristics and common pitfalls.
Performance Differences by Implementation
- HashSet 내부에 해시 테이블을 사용하여 추가, 제거, 조회 연산에 대해 평균 O(1) 성능을 제공합니다. 요소 수가 매우 많아지거나 해시 충돌이 자주 발생하면 성능이 저하될 수 있습니다.
- LinkedHashSet HashSet과 유사한 성능이지만 삽입 순서를 유지하기 위한 추가 오버헤드가 있습니다. 대부분의 경우 차이는 미미하지만 매우 큰 데이터셋을 다룰 때는 차이가 날 수 있습니다.
- TreeSet 내부에 레드-블랙 트리를 사용하여 추가, 제거, 조회 연산에 O(log n) 성능을 제공합니다. HashSet보다 느리지만 자동 정렬을 제공합니다.
가변 객체를 Set 요소로 사용할 때
Set에 가변 객체를 저장할 때는 특별히 주의가 필요합니다.
HashSet과 TreeSet은 요소를 관리하기 위해 hashCode 또는 compareTo 값을 사용합니다.
삽입 후 이러한 값이 변경되면 조회 및 제거가 실패할 수 있습니다.
예시: 가변 객체의 함정
Set<Person> people = new HashSet<>();
Person p = new Person("Taro");
people.add(p);
p.name = "Jiro"; // Modifying after insertion
people.contains(p); // May return false unexpectedly
이러한 문제를 피하려면 가능한 한 Set 요소로 불변 객체를 사용하는 것이 강력히 권장됩니다.
null 값 처리
- HashSet / LinkedHashSet : 하나의 null 요소를 허용합니다
- TreeSet : null을 허용하지 않으며 (NullPointerException을 발생시킵니다)
기타 중요한 참고 사항
- 반복 중 수정 Set을 반복하면서 수정하면
ConcurrentModificationException이 발생할 수 있습니다. Set을 직접 수정하는 대신Iterator.remove()를 사용하세요. - 올바른 구현 선택 순서가 중요할 때는
LinkedHashSet이나TreeSet을 사용합니다.HashSet은 순서를 보장하지 않습니다.
7. 비교 차트 (개요)
아래 표는 주요 Set 구현체들의 차이점을 쉽게 비교할 수 있도록 요약한 것입니다.
| Implementation | No Duplicates | Order Preserved | Sorted | Performance | null Allowed | Typical Use Case |
|---|---|---|---|---|---|---|
| HashSet | Yes | No | No | Fast (O(1)) | One allowed | Duplicate removal, order not required |
| LinkedHashSet | Yes | Yes (Insertion order) | No | Slightly slower than HashSet | One allowed | Duplicate removal with order preservation |
| TreeSet | Yes | No | Yes (Automatic) | O(log n) | Not allowed | Duplicate removal with sorting |
주요 요점
- HashSet : 순서가 중요하지 않고 성능이 중요한 경우 기본 선택입니다.
- LinkedHashSet : 삽입 순서를 유지해야 할 때 가장 적합합니다.
- TreeSet : 자동 정렬이 필요할 때 이상적입니다.
8. 자주 묻는 질문 (FAQ)
Q1. 기본 타입(int, char 등)을 Set에 사용할 수 있나요?
A1. 사용할 수 없습니다. 대신 Integer나 Character와 같은 래퍼 클래스를 사용하세요.
Q2. 동일한 값을 여러 번 추가하면 어떻게 되나요?
A2. 첫 번째 삽입만 저장됩니다. 요소가 이미 존재하면 add 메서드는 false를 반환합니다.
Q3. List와 Set은 언제 사용해야 하나요?
A3. 순서나 중복이 중요하면 List를 사용하고, 고유성이 필요하면 Set을 사용하세요.
Q4. 사용자 정의 객체를 Set에 저장하려면 무엇이 필요합니까?
A4. equals와 hashCode를 적절히 오버라이드해야 합니다.
Q5. 삽입 순서를 유지하려면 어떻게 해야 하나요?
A5. LinkedHashSet을 사용하세요.
Q6. 요소를 자동으로 정렬하려면 어떻게 해야 하나요?
A6. TreeSet을 사용하세요.
Q7. Set에 null 값을 포함할 수 있나요?
A7. HashSet과 LinkedHashSet은 하나의 null을 허용하지만, TreeSet은 허용하지 않습니다.
Q8. Set의 크기를 어떻게 확인하나요?
A8. size()를 사용하세요.
Q9. Set을 List나 배열로 변환하려면 어떻게 해야 하나요?
A9.
- 배열로:
toArray() - List로:
new ArrayList<>(set)
Q10. 반복 중에 요소를 제거할 수 있나요?
A10. 예, 하지만 Iterator.remove()를 사용해야 합니다.
9. 결론
이 문서는 Java Set 컬렉션을 기본 개념부터 고급 사용법까지 다루었습니다. 주요 내용은 다음과 같습니다:
- Set은 고유한 요소들의 컬렉션을 관리하도록 설계되어 중복 제거에 이상적입니다.
- 주요 구현체로는 HashSet(빠르고 순서 없음), LinkedHashSet(삽입 순서 유지), TreeSet(정렬됨)이 있습니다.
- 일반적인 사용 사례는 중복 제거, 고유성 검사, 사용자 정의 객체 관리, 빠른 조회 등입니다.
- 성능 특성 및 가변 객체와 반복 규칙과 같은 함정을 이해하는 것이 중요합니다.
- 비교 표와 FAQ는 실제 개발에 유용한 실용적인 가이드를 제공합니다.
Set 컬렉션을 마스터하면 Java 프로그래밍이 더 깔끔하고, 안전하며, 효율적이 됩니다.
다음으로, Set을 List 또는 Map과 결합하여 보다 고급 데이터 구조와 솔루션을 구축하는 것을 고려해 보세요.

