카테고리 없음

Hash Set

hojin98 2026. 1. 28. 21:52
"Hash Set"

 

 

Hash Set은 순서를 유지하지 않고, 중복도 허용하지 않는다.

 

Set인터페이스를 구현한 대표적인 class는

 

1. Hash Set

2. Tree Set

 

 

 

1. Hash Set

 

  • Set 인터페이스를 구현한 대표적인 컬렉션 클래스
  • 순서를 유지하려면 LinkedHashSet클래스를 사용하면 된다.

 

2. Tree Set

 

  • 범위 검색(from ~ to)과 정렬에 유리한 컬렉션 클래스
  • Hash Set보다 데이터 추가, 삭제에 시간이 더 걸

 

 

 


 

Hash Set의 주요 메서드

 

 

 

  • HashSet( )
  • HashSet(Collection c) - 생성자 (지정하는 컬렉션에 모든 객체를 저장)

 

  • HeshSet(int initialCapacity) - 초기용량 (처음 크기 설정, 보통 2배로 설정함)
  • HeshSet(int initialCapacity, float loadFactory)
    • float loadFactory >> 언제 2배로 늘릴건지, 예를들어 0.8로 해놓으면 80% 찼을 때 2배로 늘린다.

 

컬렉션 클래스들은 공간이 부족하면 스스로 공간을 늘림

 

 

추가하는 메서드

  • boolean add(Object o)
  • boolean addAll(Collection c) - 합집합

 

삭제하는 메서드

  • boolean remove(Object o)
  • boolean removeAll(Collection c) - 교집합
  • void clear( ) - 모두 삭제
  • boolean retainAll(Collection c) - 컬렉션에 있는것만 남기고 나머지 삭제 (조건부 삭제, 차집합)

 

 

  • boolean contains(Object o) - Set이 해당 객체가 포함되어 있는지 알려줌,
    • 포함되어 있으면 -> true, 포함되어 있지 않으면 -> false
  • boolean containsAll(Collection c) - 해당 컬렉션에 담긴 여러 객체가 모두 포함되어 있는지?
  • Iterator iterator( )

 

  • boolean isEmpty( ) - 비어있는지 확인
  • int size( ) - 저장되어 있는 객체의 갯수
  • Object[ ] toArray( ) - Set에 저장되어 있는 객체를 객체배열로 반환
  • Object[ ] toArray(Object[ ] a) - Set에 저장되어 있는 객체를 객체배열로 반환

 


코드 연습

 

 

public static void main(String[] args) {
    Object[] objArr = {"1", new Integer(1), "2", "2", "3", "3", "4", "4", "4"};
    Set set = new HashSet();

    for(int i = 0; i < objArr.length; i++){
        set.add(objArr[i]);
    }

    System.out.println(set);

    Iterator it = set.iterator();

    while(it.hasNext()){
        System.out.println(it.next());
    }
}

 

 

배열을 선언하고, for문을 이용해 Set에 배열의 요소를 하나씩 저장했다.

 

이 코드를 출력해보면

 

[1, 1, 2, 3, 4]
1
1
2
3
4

 

이러한 결과가 출력된다. 여기서 알 수 있는 것은

배열을 Object[] objArr = {"1", new Integer(1), "2", "2", "3", "3", "4", "4", "4"}; 이렇게 선언했지만,

Set은 순서와 중복을 허용하지 않기 때문에, 문자열 2,3,4는 1개씩만 저장된 것을 확인할 수 있다.

 

하지만, 1의 경우 문자열 1과 Integer객체의 1 두개가 있는데 각자 다른 객체라서 둘 다 저장이 되어있는 것이다.

>> 저장 순서를 유지하지 않기 때문에 어떤게 문자열 1이고 어떤게 Integer 1인지 정확히 알 수 없음

 

 

Iterator의 사용법은 Iterator it = set.iterator(); 메서드를 얻어오고,

it.hasNext( )를 먼저 사용하여 읽어 올 요소가 남아있는지 확인해주고, it.next( )를 사용하여 요소를 하나씩 꺼내온다.

 

여기서는 while문을 사용하여 계속 반복을 하다가, 더 이상 꺼내올 요소가 없어지면 it.hasNext( )가 false를 반환해서 반복이 멈추게되는 원리이다.

 

여기서 출력된 결과물을 보면 저장순서와 일치하는 것 처럼 보이지만, 실제로는 저장 순서와 일치한다는 보장이 없다.

 


코드 연습

 

 

public class EX11_10 {
    public static void main(String[] args) {
        Set set = new HashSet();

//TODO  Set의 크기가 6보다 작은 동안 1~45 사이의 난수를 저장
        for(int i = 0; set.size() < 6; i++){
            int num = (int)(Math.random() * 45) + 1;
//            set.add(new Integer(num));
            set.add(num);
        }
//TODO Set은 정렬을 할 수가 없음, sort의 매개변수로 올 수 있는건 List만 가능
//        정렬 >> 순서 유지가 되야함 그래서 Set은 정렬 불가
//        Set을 List에 옮기고 List를 정렬
//        1. Set의 모든 요소를 List에 저장
//        2. List list를 정렬
//        3. List list를 출력

        List list = new LinkedList(set); // LinkedList(Collection c)
        Collections.sort(list);          // Collections.sort(List list)
        System.out.println(list);
    }
}

 

 

HashSet을 만들고, HashSet의 size가 6보다 작은 동안, 1~45 사이의 난수(random값)를 얻어서 계속 추가하고 있다.

출력해보면 [3, 18, 23, 31, 37, 44] 결과가 출력이 된다. 참고로 Math.random() 메서드를 사용했기 때문에 실행할 때마다 저장되는 값이 다르게 나온다. 그리고 지금은 Set의 요소들을 List로 옮겨서 출력을 하기 때문에, 순서가 유지되면서 출력이 되는데,

만약 List 코드를 주석처리하고, set만 출력해보면 순서 유지가 안되는 상태로 출력이 된다.

 

여기서 Set을 정렬하기 위해서는 Collections.sort를 사용해야 하는데, sort의 매개변수로 올 수 있는게 List만 가능하기 때문에 원래 Set은 정렬할 수가 없다.

 

좀 더 디테일한 이유로는 순서 유지가 되야지만 정렬을 할 수 있는데, Set은 순서 유지를 하지 않으므로 정렬할 수 없다.

 

그래서 Set을 정렬하기 위해서는

 

1. Set의 모든 요소들을 List에 저장한.

2. Collections.sort( )를 사용하여 list를 정렬한다.

3. list를 출력한다.

 

List list = new LinkedList(set); // LinkedList(Collection c)
Collections.sort(list);          // Collections.sort(List list)
System.out.println(list);

 

그래서 이런식으로 Set의 모든 요소를 List에 저장하여 정렬 하고 출력한 것이다.

 

참고로 대부분의 컬렉션 클래스들은 생성자에 컬렉션을 받는 생성자가 있음,

>> LinkedList(Collection c)

다른 컬렉션으로 쉽게 바꿀 수 있는 방법들을 제공해준다.

 

 

 


 

 

 

또 다른 예제를 통해 HashSet에 대해 좀 더 알아보자,

 

 

 

 

HeshSet은 순서를 유지하지 않고, 중복을 허용하지 않기 때문에,

객체를 저장하기 전에 기존에 같은 객체가 있는지 확인을 해서 같은 객체가 없으면 저장하고, 있으면 저장하지 않는다.

 

 

 

 

boolean add(Object o)는 저장하기 전에

equals( ) hashCode( )를 호출하여 중복인지 아닌지 확인을 한다.

 

 

 

 

equals( )와 hashCode( ) 메서드는 사실 Object 클래스의 메서드이다.
그래서 대부분의 클래스들이 직접 갖고있진 않지만 Object클래스를 상속 받기 때문에, 사용할 수 있다.

 

 

 

 

단, equals( )와 hashCode( )가 오버라이딩 되어 있어야 한다.

 

 

 

 

Hash가 붙은 클래스들은 hashCode( )메서드를 내부적으로 이용을 하는데,

이러한 클래스들을 자주 활용하기 때문에 equals( )를 오버라이딩 할 때 hashCode( )도 오버라이딩 해주는게 정석이다.

 

 


equals( ), hashCode( ) 예시

 

public boolean equals(Object obj) {
if (!(obj instanceof Person)) return false;

instanceof를 통해 Object가 Person의 인스턴스가 아니면 false를 반환

Person tmp = (Person)obj;
Person의 인스턴스가 맞으면 형변환

참조변수 형변환 전에는 반드시 instanceof로 체크해줘야한다.
형변환을 해서 리모콘을 바꿔줘야지만 tmp.name , tmp.age를 사용할 수 있다.

return name.eqauls(tmp.name) && this.age == tmp.age;
}
==> this(객체 자신)과 obj(매개변수) 비교

public int hashCode( ) {
     return (name + age).hashCode( );

equals( ) >> iv를 비교

 

 

 

 

 


코드 연습

 

 

 

 


Person 클래스

class Person {
    String name;
    int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String toString() {
        return name + " " + age;
    }
}

 

 

 

 


Main

 

public class Ex11_11 {
    public static void main(String[] args) {
        HashSet set = new HashSet();

        set.add("abc");
        set.add("abc"); // 중복이라 저장 안됨
        set.add(new Person("David", 10));
        set.add(new Person("David", 10));
//        TODO : 현재 이상태로는 Person 클래스의 객체가 2개가 다 저장됨
//         그 이유는 equals()와 hashCode()가 없어서 그럼
        System.out.println(set);
    }
}

 

HashSet은 순서와 중복을 허용하지 않기 때문에 이대로 출력을 해보면 abc는 중복이여서 한개만 저장되어 출력이 되는데, Person클래스의 객체는 (David, 10)이 2개 모두 저장되는 것을 확인할 수 있다. 그 이유는 Person 클래스에 equals( )와 hashCode( )를 오버라이딩 하지 않았기 때문이다.

 

boolean add(Object o)는 저장하기 전에 equals( ) hashCode( )를 호출하여 중복인지 아닌지 확인을 해야한다.

 

 

 

 

 


Person 클래스에 equals( ), hashCode( ) 오버라이딩

 

 

//TODO equals()와 hashCode()를 오버라이딩해야 HashSet이 바르게 동작
class Person {
    String name;
    int age;

    @Override
    public int hashCode() {
//        TODO int hash(Object...value); // 가변인자여서 ()괄호 안의 값을 얼마든지 넣을 수 있음,
//         단, 필드값이 존재해야 에러가 안나겠지?
        return Objects.hash(name, age); //TODO iv들을 매개변수로 넣어주면 된다.
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof Person)) return false;

//        TODO Object 객체에는 name이란 멤버가 없기 때문에 반드시 형변환!
        Person p = (Person) obj;
//        TODO 나 자신(this)의 이름과 나이를 p와 비교
        return this.name.equals(p.name) && this.age == p.age;
    }
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String toString() {
        return name + " " + age;
    }
}

 

이런식으로 equals( )와 hashCode( )를 오버라이딩 해주고 Main에 출력해보면 (David, 10) Person 클래스의 객체가 1개만 저장되어 출력되는 것을 확인할 수 있다.

 

여기서 주의할 점은 equals를 오버라이딩 할 때는 반드시 형변환을 고려해줘야 한다.

지금 매개변수로 받아와서 비교하는 대상이 Object 객체인데, Object 객체에는 String name과 int age라는 필드값(멤버)이 없고, Person에는 필드값(멤버)이 선언되어 있기 때문에 형변환을 해서 리모콘을 바꿔줘야지만 name과 age를 비교할 수 있다.

 

그리고 형변환 하기 전에 반드시 instanceof를 사용하여 체크해줘야한다.

 

 


합집합, 교집합, 차집합 코드

 

 

HashSet에서 합집합, 교집합, 차집합을 코드로 구현해보자,

 


Main

 

public class Ex11_12 {
    public static void main(String[] args) {
        HashSet setA = new HashSet();
        HashSet setB = new HashSet();
        HashSet setHab = new HashSet();
        HashSet setKyo = new HashSet();
        HashSet setCha = new HashSet();

        setA.add("1");   setA.add("2");   setA.add("3");
        setA.add("4");   setA.add("5");
        System.out.println("A= " + setA);

        setB.add("4");   setB.add("5");   setB.add("6");
        setB.add("7");   setB.add("8");
        System.out.println("B= " + setB);

 


교집합

 

TODO 교집합
        Iterator it = setB.iterator();
//        TODO B에서 iterator로 하나씩 읽어옴
        while(it.hasNext()){
            Object tmp = it.next();
            if(setA.contains(tmp))
//          TODO contains로 4가 setA에 있는지 확인
                setKyo.add(tmp);
//          TODO true면 setKyo에 저장
        }

 

contains( ) >> 포함하고 있는지 확인하는 메서드

 

iterator

  • hasNext( ) >> 읽어 올 요소가 있는지 확인해주는 메서드
  • next( ) >> 요소를 읽어오는 메서드

setB를 iterator를 활용하여 hasNext( )로 읽어 올 요소가 있는지 확인해주고, it.next( )메서드를 통해 setB의 요소를 하나씩 읽어온다.

이 때 it.next( )를 tmp변수에 담아주고, setA.contains(tmp)를 통해 setB의 요소가 setA에 포함되어 있는지 확인해준다.

 

setA= [1, 2, 3, 4, 5]
setB= [4, 5, 6, 7, 8]

 

4가 포함? -> 5가 포함? -> 6이 포함? -> 7이 포함? -> 8이 포함?

이런식으로 하나씩 요소를 읽어오며 setA에 포함되어 있는지 확인해준다.

 

그리고 포함되어 있으면 true가 되어 setKyo.add(tmp) >> setKyo에 저장한다. [4, 5]

 

 


차집합

 

TODO 차집합
        it = setA.iterator();
        while(it.hasNext()){
            Object tmp = it.next();
            if(!setB.contains(tmp))
//          TODO !setB >> contains로 B에 없는지 확인
                setCha.add(tmp);
//          TODO true면 즉, setB에 없는 것만 setCha에 저장
        }

 

이번에는 setA를 iterator를 활용하여 setA의 요소들을 하나씩 읽어온다.

!setB contains >> B에 포함되어 있지 않다는 의미

!setB.contains(tmp) >> setA의 요소가 setB에 없는지 확인한다.

setB에 없는 것만 true >> setCha.add(tmp), setCha에 저장한다.

 

setA= [1, 2, 3, 4, 5]
setB= [4, 5, 6, 7, 8]

 

1,2,3은 setB에 없으니 저장한다. [1, 2, 3]

 

 


합집합

 

 

TODO 합집합
//        set은 순서와 중복이 없기 때문에 iterator로 setA와 setB를
//        전부 불러와서 저장하면 합집합이 된다.
        it = setA.iterator();
        while(it.hasNext())
            setHab.add(it.next());
        
        it = setB.iterator();
        while(it.hasNext())
            setHab.add(it.next());

 

합집합은 간단하다, HashSet은 순서와 중복을 허용하지 않기 때문에, setA와 setB를 전부 집어넣으면 된다.

 

 

 


이 코드를 더 간단하게

 

 

 

교집합

TODO 더 간단히 할 수 있는 메서드
//        TODO 교집합 / retainAll()
        setA.retainAll(setB); //TODO setA에서 setB에 공통된 요소만 남기고 삭제

 

 

setA= [1, 2, 3, 4, 5]
setB= [4, 5, 6, 7, 8]

 

setA.retainAll(setB); >> setA에서 setB에 있는 것만 남김

공통된 요소만 남기고 나머지는 삭제 >> [4, 5]

 

 

 

합집합

TODO 합집합 addAll()
        setA.addAll(setB); //TODO setA에 setB의 모든 요소를 추가
//        TODO Hashset이기 때문에 중복된건 제외됨

 

 

setA= [1, 2, 3, 4, 5]
setB= [4, 5, 6, 7, 8]

 

setA.addAll(setB); >> setA에 setB의 모든 요소를 추가

HashSet이여서 중복은 제외됨

[1, 2, 3, 4, 5, 6, 7, 8]

 

 

 

차집합

TODO 차집합 removeAll()
        setA.removeAll(setB); //TODO setA에서 setB와 공통 요소를 제거

 

 

setA= [1, 2, 3, 4, 5]
setB= [4, 5, 6, 7, 8]

 

setA.removeAll(setB); >> setA에서 setB와 공통요소를 제거

[1, 2, 3]