Set이란?
중복을 허용하지 않는 데이터 모음
Set은 순서를 보장하지 않고, 중복을 허용하지 않는 컬렉션입니다.
대표 구현체는 HashSet, LinkedHashSet, TreeSet이 있습니다.
예를 들어,
- 회원 ID가 같은 ID가 있으면 안 되겠죠?
- 주민번호도 중복으로 존재하면 안 됩니다!
이런 상황일 때 Set을 사용합니다.
Set<String> ids = new HashSet<>();
ids.add("wooZzing");
ids.add("test");
ids.add("wooZzing"); // ID 중복!! ❌ 안 들어감!
System.out.println(ids); // [wooZzing, test]
✔️ 결과: 중복된 "wooZzing"은 한 번만 저장됨
Set을 사용하는 이유
상황 예시 | Set이 적합한 이유 |
출석부, 회원 ID, 이메일 관리 | 중복 제거가 핵심! |
중복 없이 태그, 카테고리 수집 | 같은 항목 여러 번 안 들어가게 하려면 필요 |
빠르게 값이 있는지 확인하고 싶을 때 | contains()가 빠르게 검색 |
Set 종류 비교
클래스 | 중복 허용 | 순서 유지 | 정렬 여부 | 특징 |
HashSet | ❌ | ❌ | ❌ | 가장 일반적인 Set. 빠른 성능. |
LinkedHashSet | ❌ | ✅ | ❌ | 입력한 순서 유지. 예측 가능한 순서. |
TreeSet | ❌ | 🔺 (정렬) | ✅ | 자동으로 정렬됨. 비교기 필요. |
HashSet
중복 없는 데이터를 빠르게 저장하는 Set
- 내부적으로 Hash(해시) 알고리즘을 이용해 저장
- 순서를 보장하지 않음 (입력 순서 유지 ❌)
- 중복은 자동으로 제거됨
- null도 저장 가능 (한 개만 허용)
Set<String> set = new HashSet<>();
set.add("Java");
set.add("Spring");
set.add("Java"); // 중복 ❌ 무시됨
System.out.println(set); // [Java, Spring] (순서 랜덤)
특징
특징 | 설명 |
✅ 중복 제거 | 같은 값은 한 번만 저장됨 |
✅ 빠른 검색 | 내부적으로 HashMap을 사용해서 contains() 속도 빠름 |
❌ 순서 없음 | 넣은 순서와 출력 순서는 다를 수 있음 |
✅ null 허용 | null 값도 저장 가능 (단, 하나만) |
HashSet 내부 동작 원리
"우진"을 추가한다 → hashCode()로 해시값 계산
→ 그 위치에 저장 (버킷)
→ 같은 해시값이 있을 땐 equals()로 비교해 진짜 같은지 체크
→ 같으면 중복으로 판단 → 안 들어감!
즉, HashSet은 이 두 가지 메서드를 이용해 중복을 체크합니다.
- hashCode() → 빠르게 위치 찾기
- equals() → 진짜 같은지 확인
실습 예제
List<String> emails = List.of("a@test.com", "b@test.com", "a@test.com");
Set<String> uniqueEmails = new HashSet<>(emails);
System.out.println(uniqueEmails); // [a@test.com, b@test.com]
사용자 정의 객체 사용할 땐?
✅ 반드시 equals()와 hashCode()를 재정의 해야 합니다!
class Person {
String name;
public Person(String name) {
this.name = name;
}
// 이름이 같으면 같은 사람으로 취급
@Override
public boolean equals(Object o) {
if (o instanceof Person) {
return this.name.equals(((Person) o).name);
}
return false;
}
@Override
public int hashCode() {
return name.hashCode();
}
}
* 실행 클래스
Set<Person> set = new HashSet<>();
set.add(new Person("우진"));
set.add(new Person("우진")); // 중복! 안 들어감
System.out.println(set.size()); // 1
🚫 재정의 안 하면?
Set<Person> set = new LinkedHashSet<>();
set.add(new Person("우진"));
set.add(new Person("우진")); // ❌ 중복인데, 다르게 인식됨!
System.out.println(set.size()); // 2 ← 중복 제거 실패
주의사항
항목 | 주의할 점 |
순서 | 넣은 순서 유지 ❌ (랜덤 출력 가능성 있음) |
null 값 | 한 개만 저장 가능 |
사용자 객체 | equals() + hashCode() 꼭 재정의 |
성능 | 데이터가 많아도 검색/삽입 빠름 (O(1) 평균) |
LinkedHashSet
중복 없는 데이터를 저장하면서, 입력 순서도 유지하는 Set
- 내부적으로 HashSet + LinkedList의 구조를 가짐
- 순서는 입력한 순서대로 유지됨
- 중복은 자동 제거
- 검색 속도도 빠른 편 (거의 HashSet 수준)
Set<String> set = new LinkedHashSet<>();
set.add("Java");
set.add("Spring");
set.add("Python");
set.add("Java"); // 중복 ❌ 무시됨
System.out.println(set);
// [Java, Spring, Python] ← 입력한 순서 그대로 출력됨!
✔️ HashSet과 달리, 출력 순서가 예측 가능해서 가독성이 좋음
특징
특징 | 설명 |
중복 제거 | HashSet처럼 동일한 값은 한 번만 저장됨 |
순서 보장 | 넣은 순서대로 꺼낼 수 있음 (LinkedList 구조 덕분) |
빠른 검색 | 내부적으로 Hash 구조 사용 → 검색도 빠름 |
null 허용 | null도 저장 가능 (단, 한 개만) |
LinkedHashSet 내부 동작 원리
- 데이터를 넣으면 HashMap처럼 빠르게 저장 위치를 찾고,
- 동시에 LinkedList처럼 입력 순서를 연결 포인터로 기억해 둡니다.
입력 순서 기억하는 Linked 리스트
Java → Spring → Python
동시에 해시로 빠른 검색
(Java의 hashCode() → 위치 계산)
👉 그래서 빠르면서도 순서 보장이 가능해요!
언제 사용할까요?
- 중복 제거 + 입력 순서 유지가 중요할 때
➡ 가독성이 높고, 예측 가능한 결과를 얻을 수 있어요 - 웹 로그, 방문자 기록, 메뉴 순서 저장 등
➡ 기록된 순서 그대로 보이게 하려면 필수!
실습 예제
List<String> input = List.of("B", "A", "C", "A", "B");
Set<String> ordered = new LinkedHashSet<>(input);
System.out.println(ordered); // [B, A, C] ← 입력 순서 + 중복 제거!
LinkedHashSet도 내부적으로 HashSet 기반이라서,
👉 사용자 정의 객체를 넣을 때 주의할 점이 완전히 동일하게 적용됩니다!
TreeSet
정렬된 순서로 중복 없는 데이터를 저장하는 Set
- 내부적으로 이진 탐색 트리(Red-Black Tree) 구조 사용
- 데이터를 자동 정렬하면서 저장
- 중복은 자동으로 제거됨
- 순서는 정렬 기준에 따라 고정됨 (입력 순서 아님!)
Set<Integer> set = new TreeSet<>();
set.add(30);
set.add(10);
set.add(20);
set.add(10); // 중복 ❌ 무시됨
System.out.println(set); // [10, 20, 30] ← 자동 오름차순 정렬됨!
특징
특징 | 설명 |
중복 제거 | Set 계열이라 중복 자동 제거 |
자동 정렬 | 데이터가 들어가자마자 정렬됨 (Comparable 또는 Comparator) |
입력 순서 유지 ❌ | 입력 순서가 아닌, 정렬 순서대로 유지 |
검색 속도 빠름 | O(log n) 수준의 성능 |
실습 예제
Set<String> names = new TreeSet<>();
names.add("민수");
names.add("지민");
names.add("우진");
System.out.println(names); // [민수, 우진, 지민] → 가나다순 정렬
정렬 기준 바꾸기 – Comparator 사용
Set<String> set = new TreeSet<>(Comparator.reverseOrder());
set.add("banana");
set.add("apple");
set.add("cherry");
System.out.println(set); // [cherry, banana, apple] ← 내림차순 정렬!
사용자 정의 객체 사용 시 주의사항
❗ 반드시 Comparable 또는 Comparator 설정해야 합니다!
TreeSet은 데이터를 자동 정렬하며 저장하기 때문에,
어떤 기준으로 정렬할지 명확히 알려줘야 합니다.
방법 1: 객체 클래스 내 Comparable 인터페이스 구현
class Person implements Comparable<Person> {
String name;
public Person(String name) {
this.name = name;
}
@Override
public int compareTo(Person o) {
return this.name.compareTo(o.name); // 이름 기준으로 오름차순 정렬
}
}
방법 2: TreeSet 생성 시 Comparator 지정
Set<Person> people = new TreeSet<>(Comparator.comparing(p -> p.name));
❗ TreeSet은 compareTo() 결과로 중복 여부도 판단합니다!
Set<Person> set = new TreeSet<>();
set.add(new Person("우진"));
set.add(new Person("우진")); // compareTo() 결과가 0 → 중복으로 판단됨
System.out.println(set.size()); // 1
compareTo() 또는 Comparator.compare()의 결과가 0이면
→ 이미 있는 값과 같다고 보고 추가되지 않습니다.
즉,
TreeSet은 정렬 기준 == 중복 판단 기준입니다.
equals()랑 hashCode() 안 재정의해도 되나요?
➡ 네, TreeSet은 아예 equals()/hashCode()를 보지 않습니다.
대신 compareTo()에서 "같다(==0)"고 판단되면 Set 내부에 이미 있다고 간주합니다.
요약정리
항목 | HashSet | LinkedHashSet | TreeSet |
중복 제거 | ✅ | ✅ | ✅ |
순서 유지 | ❌ | ✅ (입력 순서 유지) | ✅ (정렬 기준 순) |
자동 정렬 | ❌ | ❌ | ✅ |
null 허용 | ✅ (1개) | ✅ (1개) | ❌ (Exception 발생) |
사용자 객체 기준 | equals/hashCode | equals/hashCode | compareTo or Comparator |
읽어주셔서 감사합니다 😊
'프로그래밍 언어 > Java' 카테고리의 다른 글
[Java] 37. List란? (ArrayList, LinkedList, Vector) (0) | 2025.04.22 |
---|---|
[Java] 36. 컬렉션 프레임워크란? (0) | 2025.04.22 |
[Java] 35. throw vs throws 차이 완벽 정리 (0) | 2025.04.22 |
[Java] 34. 사용자 정의 예외 만들기 (0) | 2025.04.21 |
[Java] 33. 예외(Exception) 처리 하기 (0) | 2025.04.21 |