Set 인터페이스 정리

hee's 정리노트·2025년 5월 4일

Java

목록 보기
4/4

📌 Set 인터페이스란?

  • 순서가 중요하지 않은 요소를 다룰 때 주로 사용
  • Collection 인터페이스의 하위 인터페이스
  • 중복을 허용하지 않음
  • 각 요소의 고유성 보장
  • null 값 저장 가능 (단, 하나만)

요소의 정렬 여부는 구현체에 따라 다름:

구현체특징
HashSet순서 보장 X
LinkedHashSet삽입 순서 유지
TreeSet자동 정렬 (오름차순 기본)

🔷 주요 메소드

메소드설명
add(E e)요소 추가 (이미 존재하면 추가 X)
remove(Object o)요소 제거
contains(Object o)존재 여부 확인
size()요소 개수 반환
isEmpty()비어 있는지 확인
clear()모든 요소 제거

📌 HashSet

  • Set 인터페이스의 대표 구현체
  • 내부적으로 HashMap을 기반으로 동작
  • 순서 보장 X
  • 중복 허용 X, null 저장 가능
  • 해시 함수를 이용해 빠른 접근 (평균 시간 복잡도: O(1))

🔷 동작 원리

  1. hashCode() 값 계산

    • 요소의 해시값을 이용해 배열의 인덱스를 결정
    • 예: 배열 길이 5 → hashCode % 5로 저장 위치 결정
  2. 해시 충돌(Collision) 처리

    • 같은 해시값 → 같은 버킷
    • Java 7까지는 LinkedList, Java 8부터는 일정 개수 초과 시 Red-Black Tree로 변환
  3. equals()로 중복 여부 확인

    • 해시값이 같더라도 equals()로 비교하여 정확한 중복 여부 판별
  4. 저장

    • add() 메소드는 내부적으로 HashMap.put() 사용

💻 예제 코드

import java.util.HashSet;

public class Main {
    public static void main(String[] args) {
        HashSet<String> set = new HashSet<>();
        set.add("apple");
        set.add("banana");
        set.add("apple"); // 중복 무시
        System.out.println(set); // [banana, apple] (순서 보장 X)
    }
}

🔷 HashSet에서 객체를 저장할 때hashCode()와 equals()를 재정의해야 하는 이유

Java에서 HashSet, HashMap, Hashtable과 같은 해시 기반 컬렉션은 객체를 저장하거나 검색할 때 내부적으로 hashCode()equals() 메소드를 사용

이 메소드들을 제대로 재정의하지 않으면, 중복 체크나 검색이 제대로 동작하지 않아 예상치 못한 결과를 낳을 수 있습니다.


🔶 Hash 기반 컬렉션의 동작 원리

  1. 객체의 hashCode()를 호출해서 저장할 버킷(bucket) 위치를 찾음
  2. 해당 버킷에 데이터가 존재하면, equals()를 호출해서 동일 객체인지 비교
    • equals() 결과가 true → 중복으로 판단하고 추가하지 않음
    • false → 같은 버킷에 저장 (해시 충돌 처리)

⚠️ 재정의를 안 하면 생기는 문제

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;
    }

    // hashCode()를 재정의하지 않음
}

public class Main {
    public static void main(String[] args) {
        HashSet<Person> set = new HashSet<>();
        set.add(new Person("Alice"));
        set.add(new Person("Alice")); // 서로 다른 객체지만 equals는 true → hashCode는 달라서 다른 버킷에 저장됨
        System.out.println(set.size()); // 출력: 2
    }
}
✅ 재정의 한 경우
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(); // 문자열의 hashCode 사용
    }
}

public class Main {
    public static void main(String[] args) {
        HashSet<Person> set = new HashSet<>();
        set.add(new Person("Alice"));
        set.add(new Person("Alice")); // equals와 hashCode 모두 같음 → 중복으로 인식
        System.out.println(set.size()); // 출력: 1
    }
}

📌 TreeSet

  • Set 인터페이스를 구현한 클래스 중 하나로 중복을 허용하지 않으며, 요소를 자동 정렬
  • 내부적으로 TreeMap을 기반으로 동작
  • 이진 탐색 트리(Binary Search Tree)의 일종인 Red-Black Tree 구조를 사용
  • 기본 정렬은 오름차순
  • 사용자 정의 정렬을 위해 Comparator 사용 가능
  • 성능: 검색, 삽입, 삭제 O(log N)

🔷 특징

  • 중복 X, 정렬 O, 순서 O (정렬된 순서)
  • 삽입과 삭제가 많고, 정렬이 필요한 경우에 적합

💻 예제 코드

import java.util.TreeSet;

public class Main {
    public static void main(String[] args) {
        TreeSet<Integer> set = new TreeSet<>();
        set.add(30);
        set.add(10);
        set.add(20);
        System.out.println(set); // [10, 20, 30] (자동 오름차순 정렬)
    }
}

📌 LinkedHashSet

  • Set 인터페이스를 구현한 클래스 중 하나
  • 중복을 허용하지 않으면서 삽입 순서를 유지
  • 내부적으로 LinkedHashMap을 기반으로 동작
  • 정렬 기능은 없음 (순서만 유지, TreeSet과 다름)

🔷 특징

  • 중복 X, 순서 O, 정렬 X
  • 순서를 유지하고 싶지만 정렬은 필요 없는 경우에 적합

💻 예제 코드

import java.util.LinkedHashSet;

public class Main {
    public static void main(String[] args) {
        LinkedHashSet<String> set = new LinkedHashSet<>();
        set.add("B");
        set.add("A");
        set.add("C");
        System.out.println(set); // [B, A, C] - 입력한 순서 그대로 출력
    }
}

0개의 댓글