ABOUT

성능과 운영 안정성을 함께 끌어올리는 개발자입니다.

92% Positional Error Reduction
79% p95 Latency Improvement
90%+ Long Tasks Reduction

2022.02 · 한국장학재단

우수 멘티

한국장학재단 사회 리더 대학생 멘토링 IT

2022.10 · 동작구청

우수 인재상

동작구청 우수 SW 인재

2025.05 · (주) 그랩

프로그래밍 우수상

(주) 그랩 우수 프로그램 개발

2025.05 · AWSKRUG

AWS한국사용자모임 발표

AI agent 스크립트 튜닝 관련 발표

ComputerScience

Development

Engineering

Trouble Shooting

GUESTBOOK

첫 마음부터
함께 나누는 온기

방명록 작성하러 가기

SUBSCRIBE

최신소식을
편하게 만나보세요.

Set

도입

Set은 중복 없는 데이터의 집합을 표현하는 자바의 핵심 자료구조다

자바에서 데이터를 저장한다고 하면 많은 사람이 먼저 List를 떠올리지만, 실제 문제를 풀다 보면 “중복이 있으면 안 된다”는 조건이 훨씬 자주 등장합니다. 이때 가장 자연스럽게 사용하는 구조가 바로 Set입니다.

예를 들어 방문한 페이지 목록, 중복 없는 태그 모음, 중복 제거된 단어 집합, 이미 처리한 번호 목록 같은 데이터는 Set과 매우 잘 맞습니다. 그래서 Set은 단순한 저장소가 아니라, 중복 제거와 존재 여부 판단을 빠르게 처리하는 도구로 이해하는 것이 중요합니다.

정의

Set은 같은 값을 한 번만 저장하는 중복 없는 컬렉션이다

자바의 Setjava.util.Set 인터페이스로 제공되며, 같은 값이 여러 번 들어오는 것을 허용하지 않는 컬렉션입니다.

즉, 이미 들어 있는 값과 같은 값을 다시 넣으려고 하면 새로운 원소가 추가되지 않습니다. 이 특성 때문에 Set은 “중복 없는 집합”을 표현할 때 가장 적합합니다.

핵심 요약
  • Set은 중복 저장이 불가능하다.
  • Set은 “몇 번째”보다 존재 여부가 더 중요하다.
  • Set은 중복 제거와 포함 여부 확인에 매우 강하다.
한 줄 정리
Set = 중복 없는 값의 집합이라고 이해하면 가장 정확합니다.

필요성

Set은 중복 제거와 빠른 포함 여부 판단이 필요한 상황에서 가장 강력하다

데이터를 다룰 때 자주 마주치는 요구사항 중 하나가 “이미 본 값인지”, “중복된 값은 제거하고 싶다”, “유일한 값만 모으고 싶다”는 것입니다. 이런 요구를 List로 처리하면 보통 반복 탐색이 들어가서 코드도 길어지고 비효율적이 됩니다.

반면 Set은 이런 상황을 위해 존재하는 구조이기 때문에, 중복 제거와 포함 여부 확인 문제를 훨씬 자연스럽고 빠르게 해결할 수 있습니다.

Set이 중요한 이유
  • 중복 제거가 매우 쉽다.
  • 이미 존재하는지 확인하는 로직이 단순해진다.
  • 방문 기록, 태그, 고유 값 관리 같은 문제와 잘 맞는다.
  • 코딩테스트에서 중복 체크용으로 매우 자주 등장한다.

List와 차이

List가 순서 중심 구조라면, Set은 유일성 중심 구조다
항목 List Set
순서 중요 구현체에 따라 다름
중복 허용 불가
인덱스 접근 가능 불가
주요 목적 순서 있는 데이터 관리 중복 제거, 포함 여부 확인

따라서 “값을 저장한다”는 공통점만 보고 List와 Set을 비슷하게 생각하면 안 됩니다. List는 위치와 순서가 중심이고, Set은 유일성과 존재 여부가 중심입니다.

기본 사용법

add, contains, remove만 익혀도 Set의 핵심 활용은 바로 가능하다
import java.util.HashSet;
import java.util.Set;

Set<String> set = new HashSet<>();

set.add("apple");
set.add("banana");
set.add("apple"); // 중복 -> 추가되지 않음

System.out.println(set.size());           // 2
System.out.println(set.contains("apple")); // true

set.remove("banana");
System.out.println(set);
자주 쓰는 메서드
  • add(value) : 값 추가
  • contains(value) : 값 존재 여부 확인
  • remove(value) : 값 삭제
  • size() : 원소 개수 확인
  • isEmpty() : 비어 있는지 확인
  • clear() : 전체 삭제

주요 구현체

Set은 HashSet, LinkedHashSet, TreeSet이 대표적이며, 순서와 정렬 여부가 핵심 차이점이다
구현체 특징 순서/정렬
HashSet 가장 기본적이고 빠른 Set 순서 보장 안 함
LinkedHashSet 삽입 순서 유지 입력 순서 보장
TreeSet 자동 정렬 정렬 순서 유지
1. HashSet

가장 일반적으로 사용하는 Set 구현체입니다. 빠른 포함 여부 확인과 중복 제거가 중요할 때 기본 선택으로 많이 사용됩니다.

2. LinkedHashSet

Set의 중복 제거 특성을 유지하면서도 입력 순서를 기억합니다. “중복은 제거하되 입력 순서는 유지해야 한다”는 상황에 잘 맞습니다.

3. TreeSet

저장할 때부터 값을 정렬된 상태로 유지합니다. 정렬된 유일 값 집합이 필요한 경우에 적합합니다.

내부 동작 원리

HashSet은 내부적으로 해시 기반 구조를 사용해 중복 여부를 빠르게 판별한다

자바에서 가장 많이 쓰는 Set은 보통 HashSet입니다. HashSet은 내부적으로 해시 기반 구조를 활용해, 어떤 값이 이미 존재하는지 빠르게 검사합니다.

즉, Set의 “중복 허용 안 함”이라는 성질은 단순히 저장 전에 전부 비교하는 것이 아니라, 해시를 기반으로 효율적으로 판단하는 방식으로 구현됩니다.

equals와 hashCode

Set에서 사용자 정의 객체의 중복 판별은 equals와 hashCode 구현에 달려 있다

Set에 사용자 정의 객체를 넣을 때 가장 중요한 것은 “무엇을 같은 값으로 볼 것인가”입니다. HashSet은 이 판단을 위해 hashCode()equals()를 사용합니다.

따라서 두 객체가 논리적으로 같은 값을 뜻한다면, equals()hashCode()도 그 기준에 맞게 함께 재정의되어야 합니다.

import java.util.Objects;

public class Tag {
    private final String name;

    public Tag(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Tag)) return false;
        Tag tag = (Tag) o;
        return Objects.equals(name, tag.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name);
    }
}
실무 함정
가변 객체를 Set 원소로 넣고 나서 상태를 바꾸면, 이후 중복 판별이나 제거가 예상대로 동작하지 않을 수 있습니다.

시간 복잡도

HashSet은 평균적으로 매우 빠르고, TreeSet은 정렬을 제공하는 대신 log n 비용을 가진다
구현체 추가 포함 여부 확인 삭제
HashSet 평균 O(1) 평균 O(1) 평균 O(1)
LinkedHashSet 평균 O(1) 평균 O(1) 평균 O(1)
TreeSet O(log n) O(log n) O(log n)

따라서 정렬이 필요 없고 빠른 중복 체크가 핵심이면 HashSet, 입력 순서를 기억하고 싶으면 LinkedHashSet, 자동 정렬이 필요하면 TreeSet이 자연스러운 선택입니다.

순회 방법

Set은 인덱스가 없기 때문에 보통 반복문이나 iterator를 통해 순회한다
for (String value : set) {
    System.out.println(value);
}

Set은 List처럼 get(0) 같은 인덱스 접근이 없기 때문에 “순서대로 꺼내는 구조”가 아니라 “집합 전체를 순회하거나 포함 여부를 확인하는 구조”로 이해하는 편이 맞습니다.

실전 사용 예시

Set은 중복 제거, 방문 체크, 고유 값 수집 같은 패턴에서 특히 자주 사용된다
  • 중복 단어 제거
  • 방문한 노드/정점 체크
  • 이미 처리한 값인지 확인
  • 태그 목록의 유일성 유지
  • 중복 없는 사용자 ID 모음
Set<Integer> visited = new HashSet<>();

for (int num : nums) {
    if (visited.contains(num)) {
        System.out.println("중복 발견");
    }
    visited.add(num);
}

List / Map과의 관계

Set은 List보다 유일성에 강하고, Map보다 값 자체의 집합 관리에 초점이 맞춰져 있다
구조 핵심 목적
List 순서 있는 값 저장
Set 중복 없는 값 저장
Map 키로 값 찾기

즉, Set은 “값 하나하나를 유일하게 관리하는 구조”이고, Map은 “키를 이용해 값을 찾는 구조”라는 점에서 구분해야 합니다.

주의할 점

Set은 인덱스 구조가 아니며, 중복 기준은 equals와 hashCode에 의존한다는 점을 꼭 기억해야 한다
  • Set은 인덱스 접근이 불가능하다.
  • HashSet은 순서를 보장하지 않는다.
  • 중복 판별 기준은 객체의 equals/hashCode에 달려 있다.
  • 가변 객체를 넣고 내부 상태를 바꾸면 문제가 생길 수 있다.
  • 단순 방문 체크라면 Set이 Map보다 더 적절한 경우가 많다.
GOOD ✅
중복 제거와 포함 여부 확인이 핵심이면 Set부터 떠올리는 습관이 매우 중요합니다.

디버깅

Set이 예상과 다르게 동작할 때는 중복 기준, 순서 기대, 객체 변경 여부를 먼저 확인해야 한다
1
중복 기준이 되는 equals/hashCode가 올바른지 확인한다.
2
HashSet에 순서를 기대하고 있지는 않은지 점검한다.
3
원소를 넣은 뒤 내부 상태가 바뀌는 가변 객체는 아닌지 확인한다.
4
단순 존재 여부 확인이면 Map보다 Set이 더 적절한지 다시 본다.

요약

Set은 중복 없는 값의 집합을 표현하는 구조이며, HashSet이 가장 일반적인 기본 선택이다
  • ✅ Set은 중복을 허용하지 않는다.
  • ✅ 중복 제거와 포함 여부 확인 문제에 매우 강하다.
  • ✅ 대표 구현체는 HashSet, LinkedHashSet, TreeSet이다.
  • ✅ 순서가 필요 없으면 보통 HashSet부터 시작한다.
  • ✅ 사용자 정의 객체를 넣을 때는 equals와 hashCode가 중요하다.
728x90