개발공부/JAVA

[JAVA] Set 컬렉션

jnnjnn 2024. 3. 19. 17:21

List 컬렉션은 저장 순서를 유지하지만, Set 컬렉션의 HashSet의 경우 저장 순서가 유지되지 않는다. 또한 객체를 중복해서 저장할 수 없고, 하나의 null만 저장할 수 있다. Set 컬렉션은 수학의 집합에 비유될 수 있다. 집합은 순서와 상관없고 중복이 허용되지 않기 때문이다.

 

Set 컬렉션에는 HashSet, LinkedHashSet, TreeSet 등이 있는데, Set 컬렉션에서 공통적으로 사용 가능한 Set 인터페이스의 메소드는 다음과 같다. 인덱스로 관리하지 않기 때문에 인덱스를 매개값으로 갖는 메소드가 없다.

 

기능 메소드 설명
객체 추가 boolean add(E e) 주어진 객체를 성공적으로 저장하면 true를 리턴하고 중복 객체면 false를 리턴
객체 검색 boolean contains(Object o) 주어진 객체가 저장되어 있는지 여부
isEmpty() 컬렉션이 비어 있는지 조사
Iterator<E> iterator() 저장된 객체를 한 번씩 가져오는 반복자 리턴
int size() 저장되어 있는 전체 객체 수 리턴
객체 삭제 void clear() 저장된 모든 객체를 삭제
boolean remove(Object o) 주어진 객체를 삭제

 

HashSet

HashSet은 Set 인터페이스의 구현 객체로 Set 컬렉션에서 가장 많이 사용된다. 다음과 같이 HashSet 컬렉션을 생성할 수 있다.

 

Set<E> set = new HashSet<E>(); // E에 지정된 타입의 객체만 저장

 

HashSet은 동일한 객체는 중복 저장하지 않는다.

 

HashSet이 동일한 객체, 즉 동등 객체라고 판단하는 기준은 다음과 같다.

HashSet은 hashCode()의 메소드의 리턴값을 먼저 검사한다. 만약 hashCode()의 리턴값이 같다면 equals() 메소드로 두 객체를 비교해서 값을 true로 리턴하면 동일한 객체라고 판단하고 중복 저장하지 않는다.

 

hashCode()와 equals() 메소드를 오버라이딩하면 동등 객체를 어떻게 판단할지 사용자가 정의할 수 있다.

 

다음은 Member 클래스를 선언할 때 이름과 나이가 동일하다면 동일한 해시코드가 리턴되도록 hashCode()를 재정의하고, equals() 메소드가 true를 리턴하도록 재정의했다.

public class Member {
    public String name;
    public int age;

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

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Member target) {
            return target.name.equals(name) && (target.age == age);
        } else {
            return false;
        }
    }

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

public class HashSetExample {
    public static void main(String[] args) {
        Set<Member> set = new HashSet<>();

        set.add(new Member("홍길동", 30));
        set.add(new Member("홍길동", 30));

        System.out.println("총 객체 수 : " + set.size()); // 1
    }
}

 

 

 

Set 컬렉션은 인덱스로 객체를 검색해서 가져오는 메소드가 없다. 대신 객체를 한 개씩 반복해서 가져와야 하는데, 

여기에는 두 가지 방법이 있다.

 

하나는 다음과 같이 for문을 이용하는 것이다

Set<E> set = new HashSet<>();
for(E e : set) {
}

 

다른 방법은 다음과 같이 Set 컬렉션의 iterator() 메소드로 반복자(iterator)를 얻어 객체를 하나씩 가져오는 것이다. 타입 파라미터 E는 Set 컬렉션에 저장되어 있는 객체의 타입이다.

Set<E> set = new HashSet<>();
Iterater<E> iterator = set.iterator();

 

iterator는 Set 컬렉션의 객체를 가져오거나 제거하기 위해 다음 메소드를 제공한다.

 

리턴 타입 메소드명 설명
boolean hasNext() 가져올 객체가 있으면 true를 리턴하고 없으면 false를 리턴한다
E next() 컬렉션에서 하나의 객체를 가져온다
void remove() next()로 가져온 객체를 Set 컬렉션에서 제거한다

 

다음과 같이 사용한다

while(iterator.hasNext()){
	E e = iterator.next();
}

 

hasNext() 메소드로 가져올 객체가 있는지 먼저 확인하고, true를 리턴할 때만 next() 메소드로 객체를 가져온다. 만약 next()로 가져온 객체를 컬렉션에서 제거하고 싶다면 remove() 메소드를 사용한다.

 

다음은 for문과 iterator를 사용해 글자의 수가 홀수인 요소만 remove() 메소드로 삭제하는 예제이다.

public class C03Iterator {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("java");
        list.add("css");
        list.add("react");
        list.add("spring");
        list.add("html");

        // 고전적 for, 향상된 for, forEach
        System.out.println("### 고전적 for");
        for (int i = 0; i < list.size(); i++) {
//            System.out.println(list.get(i));
            String s = list.get(i);
            if (s.length() % 2 == 1) {
                list.remove(i);
            }
        }
        System.out.println(list); [java, react, spring, html]

        // 탐색 중간에 remove 잘 실행이 되지 않음

        // Iterator
        System.out.println("### Iterator");
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            String next = iterator.next();
//            System.out.println(next);
            if (next.length() % 2 == 1) {
                iterator.remove();
            }
        }
        System.out.println(list); // [java, spring, html] // 잘 실행됨

    }
}

 

for문을 사용해서 요소를 삭제할 경우 변수 i의 순서대로 실행되기 때문에 숫자가 밀려 'react'가 삭제되지 않는 문제점이 발생했다. 반면 iterator를 사용하여 요소를 삭제할 경우 잘 실행된다.