본문으로 바로가기

1. Set 인터페이스

  • 저장 순서가 유지되지 않습니다.
  • 중복 저장 불가능합니다.
  • 수학의 집합과 같은 개념입니다.(순서가 상관없고 중복이 허용되지 않음)
  • 구현 클래스로는 HashSet, LinkedHashSet, TreeSet이 있습니다.

 

2. Set 인터페이스 주요 기능

[객체 추가]
boolean add(E e) → 특정 객체 추가

[객체 검색]
boolean contains(Object o) → 특정 객체가 존재하는지 여부 
boolean isEmpty() → 비어있는지 확인
int size() → 저장된 객체 수 리턴
Iterator<E> iterator() → 반복자 리턴(루핑 시 활용)

[객체 삭제]
void clear() → 저장된 모든 객체 삭제
boolean remove(Object o) → 특정 객체 삭제

 

3. Iterator 인터페이스 주요 기능

boolean hasNext() → 가지고 올 객체가 있으면 true 없으면 false 리턴
next() → 컬렉션에서 객체를 하나 가지고 옵니다.
void remove() → Set 컬렉션에서 객체를 제거합니다. (Iterator 메소드이지만 해당 컬렉션에서 객체를 제거합니다.)

 

[루핑(looping) 예시]

// String 타입을 갖는 HashSet 컬렉션 생성
Set<String> hashSet = new HashSet<>();

hashSet.add("a");
hashSet.add("b");
hashSet.add("c");
hashSet.add("d");
hashSet.add("e");

// Iterator 이용한 루핑
Iterator<String> iterator = hashSet.iterator();
while(iterator.hasNext()) {
    System.out.println(iterator.next());
}

// foreach 이용한 루핑
for(String alpha : hashSet) {
    System.out.println(alpha);
}

// 람다, 메소드 레퍼런스 이용한 루핑
hashSet.forEach(System.out::println);

 

4. HashSet

객체를 순서 없이 저장하고 동일한 객체는 중복 저장하지 않습니다.

 

[중복 저장하지 않는 방법] 

HashSet

1. 객체를 저장 전 hashCode() 메소드를 이용해 객체의 해시 코드를 얻은 후 이미 저장되어있는 객체들의 해시 코드와 비교합니다.
2. 동일한 해시 코드가 있다면 equals() 메소드로 두 객체를 비교해 동일한 객체일 경우 중복 저장을 하지 않습니다. 

 

예시 1 (String 객체를 타입으로 갖는 HashSet)

String 클래스에는 이미 특정 메소드가 구현되어 있습니다. 따라서 중복 저장되지 않습니다. 

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {

    public int hashCode() {
        ... 
    }

    public boolean equals(Object anObject) {
        ...
    }
    
}
// String 타입을 갖는 HashSet 컬렉션 생성
Set<String> hashSet = new HashSet<>();

hashSet.add("a");
hashSet.add("a");
hashSet.add("b");
hashSet.add("b");
hashSet.add("c");

// 람다, 메소드 레퍼런스 이용한 루핑
hashSet.forEach(System.out::println); // result a b c

 

 

예시 2 (사용자 정의 객체를 타입으로 갖는 HashSet)

사용자 정의 클래스에 특정 메소드를 오버 라이딩하여 구현해줘야 합니다. 

import java.util.HashSet;
import java.util.Set;

public class SetTest {
    public static void main(String[] args) {
        Set<Person> hashSet = new HashSet<>();

        hashSet.add(new Person("kim", "010-1234-1234"));
        hashSet.add(new Person("lee", "010-1234-1234"));
        hashSet.add(new Person("jang", "010-1111-1111"));
        hashSet.add(new Person("shin", "010-1111-1111"));
        hashSet.add(new Person("suk", "010-1234-1231"));

        // 람다, 메소드 레퍼런스 이용한 루핑
        hashSet.forEach(h -> {
            System.out.println(h.getName() + " : " + h.getPhone());
        });
        /**
         * result
         * jang : 010-1111-1111
         * kim : 010-1234-1234
         * suk : 010-1234-1231
         */
    }

    // 핸드폰 번호가 같으면 같은 객체로 판단
    static class Person {
        private String name;
        private String phone;

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

        public String getName() {
            return name;
        }

        public String getPhone() {
            return phone;
        }

        @Override
        public boolean equals(Object o) {
            if(o instanceof Person) {
                Person person = (Person) o;
                return person.getPhone().equals(getPhone());
            } else {
                return false;
            }
        }

        @Override
        public int hashCode() {
            return getPhone().hashCode();
        }
    }
}

5. LinkedHashSet

LinkedHashSet도 마찬가지로 중복된 데이터를 저장할 수 없다. 그러나 입력된 순서대로 데이터를 관리한다. 

Set<String> linkedHashSet = new LinkedHashSet<>();

linkedHashSet.add("a");
linkedHashSet.add("a");
linkedHashSet.add("b");
linkedHashSet.add("b");
linkedHashSet.add("c");
linkedHashSet.add("c");
linkedHashSet.add("d");
linkedHashSet.add("d");
linkedHashSet.add("e");
linkedHashSet.add("e");

linkedHashSet.forEach(System.out::println);
/**
 * result
 * a
 * b
 * c
 * d
 * e
 */

6. TreeSet

  • 검색 기능이 강화된 컬렉션 프레임워크 구현체
  • TreeSet에는 객체가 저장되는 동시에 자동으로 오름차순으로 정렬됩니다.
  • TreeSet은 정렬을 위해 Comparable을 구현한 객체를 요구합니다.
  • Integer, Double, String은 모두 Comparable를 구현하고 있으며 사용자 정의 클래스를 사용하려면 Comparable을 구현해야 합니다.
  • 이진트리 구조를 가지고 있습니다.

[이진트리]

- 여러 개의 노드가 트리 형태로 연결된 구조

- 루트 노드에서부터 시작해 각 노드 최대 2개의 노드를 연결할 수 있는 구조

- 위아래로 연결된 두 노드 = 부모-자식 관계 (부모 노드, 자식 노드)

- 하나의 부모 노드는 최대 두 개의 자식 노드와 연결될 수 있습니다.

- 부모 노드의 값 보다 작은 노드는 왼쪽

- 부모 노드의 값 보다 큰 노드는 오른쪽

- 왼쪽 마지막 노드가 제일 작은 값

- 오른쪽 마지막 노드가 가장 큰 값

- 오름차순(왼쪽 마지막 노드에서부터 오른쪽 마지막 노드까지 [왼쪽]→[부모][오른쪽] 순으로 읽음)

- 내림차순(오른쪽 마지막 노드에서부터 왼쪽 마지막 노드까지 [오른쪽][부모][왼쪽] 순으로 읽음)

- 그룹핑에 매우 유리합니다.

 

[예시]

TreeSet<Integer> treeSet = new TreeSet<>();

treeSet.add(4);
treeSet.add(2);
treeSet.add(9);
treeSet.add(7);
treeSet.add(1);

이진트리

 

TreeSet 기능

TreeSet 같은 경우엔 Set 인터페이스 타입에 대입해서 사용해도 되지만 TreeSet 타입으로 대입해야 TreeSet이 가지고 있는 검색 관련 기능들을 사용할 수 있습니다.

Set<Integer> treeSet1 = new TreeSet<>();
TreeSet<Integer> treeSet2 = new TreeSet<>();

[검색 관련 기능]

E first() → 제일 작은 객체를 반환

E last() → 제일 큰 객체를 반환

E lower(E e) → 주어진 객체보다 작은 데이터 중 최댓값 반환(주어진 객체보다 바로 아래 객체를 반환)

E higher(E e) → 주어진 객체보다 큰 데이터 중 최솟값 반환(주어진 객체보다 바로 위 객체를 반환)

E floor(E e) → 주어진 객체와 같은 값이 있으면 반환, 없다면 주어진 객체보다 작은 데이터 중 최댓값 반환 

E ceiling(E e) 주어진 객체와 같은 값이 있으면 반환, 없다면 주어진 객체보다 큰 데이터 중 최소값 반환

E pollFirst() → 제일 작은 객체를 반환하고 컬렉션에서 제거

E pollLast() → 제일 큰 객체를 반환하고 컬렉션에서 제거

 

※ 문자열(String) 같은 경우엔 문자의 유니코드 값으로 비교합니다.

TreeSet<Integer> treeSet = new TreeSet<>();

treeSet.add(4);
treeSet.add(2);
treeSet.add(9);
treeSet.add(7);
treeSet.add(1);

System.out.println(treeSet.first()); // 1
System.out.println(treeSet.last()); // 9

System.out.println(treeSet.lower(4)); // 2
System.out.println(treeSet.higher(4)); // 7

System.out.println(treeSet.floor(3)); // 2
System.out.println(treeSet.ceiling(3)); // 4

System.out.println(treeSet.pollFirst()); // 1
System.out.println(treeSet.pollLast()); // 9

[정렬 관련 기능]

Iterator<E> descendingIterator() → 내림차순으로 정렬된 Iterator를 리턴

NavigableSet<E> descendingSet() → 내림차순으로 정렬된 NavigableSet를 리턴

 

※ 오름차순으로 정렬하고 싶으면 descendingSet() 메소드를 두 번 호출합니다.

TreeSet<Integer> treeSet = new TreeSet<>();

treeSet.add(4);
treeSet.add(2);
treeSet.add(9);
treeSet.add(7);
treeSet.add(1);

// 내림차순
NavigableSet<Integer> navigableSetDesc = treeSet.descendingSet();
navigableSetDesc.forEach(System.out::println); // 9 7 4 2 1

System.out.println("---------------------------------------------");

// 오름차순
NavigableSet<Integer> navigableSetAsc = navigableSetDesc.descendingSet();
navigableSetAsc.forEach(System.out::println); // 1 2 4 7 9

System.out.println("---------------------------------------------");

// 다른 방법 //

// 기본값은 오름차순
treeSet.forEach(System.out::println);
// treeSet.stream().sorted(Comparator.naturalOrder()).forEach(System.out::println);

System.out.println("-------------------------------------");

// Stream API sorted를 활용하여 내림차순 정렬
treeSet.stream().sorted(Comparator.reverseOrder()).forEach(System.out::println);

[범위 검색 관련 기능]

NavigableSet<E> headSet(E toElement, boolean inclusive)  

→ 주어진 객체보다 작은 객체들을 NavigableSet으로 반환, 주어진 객체 포함 여부는 두 번째 매개 값으로 설정

 

NavigableSet<E> tailSet(E fromElement, boolean inclusive)  

→ 주어진 객체보다 큰 객체들을 NavigableSet으로 반환, 주어진 객체 포함 여부는 두 번째 매개 값으로 설정

 

NavigableSet<E> subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive

→ 시작과 끝으로 주어진 객체 사이의 객체들을 NavigableSet으로 반환, 시작과 끝 객체의 포함 여부는 두 번째 네 번째 매개 값으로 설정 

TreeSet<Integer> treeSet = new TreeSet<>();

// 1 2 4 7 9
treeSet.add(4);
treeSet.add(2);
treeSet.add(9);
treeSet.add(7);
treeSet.add(1);

// 4보다 작은 객체들 반환(4 포함)
NavigableSet<Integer> headSet1 = treeSet.headSet(4, true);
headSet1.forEach(System.out::println); // 1 2 4

System.out.println("-----------------------------------");

// 4보다 작은 객체들 반환(4 미포함)
NavigableSet<Integer> headSet2 = treeSet.headSet(4, false);
headSet2.forEach(System.out::println); // 1 2

System.out.println("-----------------------------------");

// 1~9 사이의 객체 반환(1,9 포함)
NavigableSet<Integer> subSet2 = treeSet.subSet(1, true, 9, true);
subSet2.forEach(System.out::println); // 1 2 4 7 9

System.out.println("-----------------------------------");

// 1~9 사이의 객체 반환(1,9 미포함)
NavigableSet<Integer> subSet1 = treeSet.subSet(1, false, 9, false);
subSet1.forEach(System.out::println); // 2 4 7