Set 인터페이스를 구현한 클래스
종류:
- HashSet
- TreeSet
순서가 없고 중복을 허용하지 않는다
HashMap 인스턴스를 이용하여 요소를 저장하는 클래스
해시 알고리즘(hash algorithm)을 사용하여 검색 속도가 매우 빠르다
만약 요소의 저장 순서를 유지해야 한다면 JDK 1.4부터 제공하는 LinkedHashSet 클래스를 사용하면 된다
HashSet<String> hs01 = new HashSet<String>();
HashSet<String> hs02 = new HashSet<String>();
hs01.add("홍길동");
hs01.add("이순신");
System.out.println(hs01.add("임꺽정"));
System.out.println(hs01.add("임꺽정")); // 중복된 요소의 저장
for (String e : hs01) {
System.out.print(e + " ");
}
hs02.add("임꺽정");
hs02.add("홍길동");
hs02.add("이순신");
Iterator<String> iter02 = hs02.iterator();
while (iter02.hasNext()) {
System.out.print(iter02.next() + " ");
}
System.out.println("집합의 크기 : " + hs02.size());
// true
// false
// 홍길동 이순신 임꺽정
// 홍길동 이순신 임꺽정
// 집합의 크기 : 3
위 코드를 보면 중복 요소를 추가하려고 하면 false를 반환하고 순서는 상관이 없다는 것을 알 수 있음
그런데 사용자 정의 객체의 값을 비교할 때는 기본적으로 주소값으로 하기 떄문에 제대로 비교하는 것이 불가능하다
즉, 사용자 정의 클래스의 인스턴스를 HashSet에 추가하면, 동일한 데이터를 가진 두 객체라도 서로 다른 메모리 주소를 가질 수 있기 때문에 중복으로 간주되지 않습니다.
Hashset이 중복을 찾는 요소는 다음과 같다
1. 해당 요소에서 hashCode() 메소드를 호출하여 반환된 해시값으로 검색할 범위를 결정합니다.
2. 해당 범위 내의 요소들을 equals() 메소드로 비교합니다.
따라서 이를 위해서는 Object 클래스에서 제공하는 hashCode()와 equals() 메소드를 오버라이딩 하면 사용자 정의 객체의 값도 비교할 수 있다
class Animal {
String species;
String habitat;
Animal(String species, String habitat) {
this.species = species;
this.habitat = habitat;
}
public int hashCode() { return (species + habitat).hashCode(); }
public boolean equals(Object obj) {
if(obj instanceof Animal) {
Animal temp = (Animal)obj;
return species.equals(temp.species) && habitat.equals(temp.habitat);
} else {
return false;
}
}
}
public class Set02 {
public static void main(String[] args) {
HashSet<Animal> hs = new HashSet<Animal>();
hs.add(new Animal("고양이", "육지"));
hs.add(new Animal("고양이", "육지"));
hs.add(new Animal("고양이", "육지"));
System.out.println(hs.size());
}
}
해시 함수를 사용하여 데이터를 해시 테이블에 저장하고, 그것을 검색하는 알고리즘
해시함수 알고리즘
먼저 데이터의 키값을 해시 함수에 넣어 반환되는 값으로 배열의 인덱스를 구한다
이후 해당 인덱스에 저장된 연결 리스트에 데이터를 저장하게 됩니다.
검색 시에도 검색하고자 하는 값을 해시 함수에 넣어 반환되는 값으로 배열의 인덱스를 구하고 그 인덱스 범위에서 연결리스트를 통해 값을 검색하기 때문에 매우 빠르다
데이터가 정렬된 상태로 저장되는 이진 검색 트리의 형태로 요소를 저장한다
이진 검색 트리는 데이터를 추가하거나 제거하는 등의 기본 동작 시간이 매우 빠르다
TreeSet<Integer> ts = new TreeSet<Integer>();
ts.add(30);
ts.add(40);
ts.add(20);
ts.add(10);
for (int e : ts) {
System.out.print(e + " ");
}
ts.remove(40);
Iterator<Integer> iter = ts.iterator();
while (iter.hasNext()) {
System.out.print(iter.next() + " ");
}
System.out.println("이진 검색 트리의 크기 : " + ts.size());
System.out.println(ts.subSet(10, 20));
System.out.println(ts.subSet(10, true, 20, true));
10 20 30 40
10 20 30
이진 검색 트리의 크기 : 3
[10]
[10, 20]
매개변수가 2개인 subSet() 메소드는 첫 번째 매개변수로 전달된 값에 해당하는 요소부터 시작하여 두 번째 매개변수로 전달된 값에 해당하는 요소의 바로 직전 요소까지를 반환한다
매개변수가 4개인 subSet() 메소드는 두 번째와 네 번째 매개변수로 각각 첫 번째와 세 번째 매개변수로 전달된 값에 해당하는 요소를 포함할 것인지 아닌지를 명시할 수 있다.
즉, 네번째 매개변수를 false로 바꾸면 [10]
만 출력된다
Map 인터페이스를 구현한 Map 컬렉션 클래스들은 키와 값을 하나의 쌍으로 저장하는 방식(key-value 방식)을 사용한다
1. HashMap<K, V>
2. Hashtable<K, V>
3. TreeMap<K, V>
Map 인터페이스는 Collection 인터페이스와는 다른 저장 방식을 가짐
key-value 저장방식으로 인해 아래와 같은 특징을 가진다
1. 요소의 저장 순서를 유지하지 않습니다.
2. 키는 중복을 허용하지 않지만, 값의 중복은 허용합니다.
HashMap 클래스는 해시 알고리즘(hash algorithm)을 사용하여 검색 속도가 매우 빠르다
HashMap 클래스의 메소드
void clear()
: 해당 맵(map)의 모든 매핑(mapping)을 제거
boolean containsKey(Object key)
: 해당 맵이 전달된 키를 포함하고 있는지를 확인
boolean containsValue(Object value)
: 해당 맵이 전달된 값에 해당하는 하나 이상의 키를 포함하고 있는지를 확인
V get(Object key)
: 해당 맵에서 전달된 키에 대응하는 값을 반환
(만약 해당 맵이 전달된 키를 포함한 매핑을 포함하고 있지 않으면 null을 반환)
boolean isEmpty()
: 해당 맵이 비어있는지를 확인함
Set<K> keySet()
: 해당 맵에 포함되어 있는 모든 키로 만들어진 Set 객체를 반환
V put(K key, V value)
: 해당 맵에 전달된 키에 대응하는 값으로 특정 값을 매핑
(이전에 값이 없었으면 null을 반환후 값을 할당, 값이 있었으면 이전 값을 반환하고 value값을 변경한다)
V remove(Object key)
: 해당 맵에서 전달된 키에 대응하는 매핑을 제거
(없으면 null 반환 있으면 value 값 반환 후 제거)
boolean remove(Object key, Object value)
: key와 value 모두가 맵에 존재하고 일치할 때에만 해당 key-value 쌍을 제거
(제거되면 true, 없어서 제거 안되면 false를 반환)
V replace(K key, V value)
: 해당 맵에서 전달된 키에 대응하는 값을 특정 값으로 대체
(없으면 null 반환)
boolean replace(K key, V oldValue, V newValue)
: key와 value 모두가 맵에 존재하고 일치할 때만 대체
(대체되면 true, 없어서 대체 안되면 false를 반환)
int size()
: 해당 맵의 매핑의 총 개수를 반환
HashMap<String, Integer> hm = new HashMap<String, Integer>();
hm.put("삼십", 30);
hm.put("십", 10);
hm.put("사십", 40);
hm.put("이십", 20);
System.out.println("맵에 저장된 키들의 집합 : " + hm.keySet());
for (String key : hm.keySet()) {
System.out.println(String.format("키 : %s, 값 : %s", key, hm.get(key)));
}
hm.remove("사십");
Iterator<String> keys = hm.keySet().iterator();
while (keys.hasNext()) {
String key = keys.next();
System.out.println(String.format("키 : %s, 값 : %s", key, hm.get(key)));
}
hm.replace("이십", 200);
for (String key : hm.keySet()) {
System.out.println(String.format("키 : %s, 값 : %s", key, hm.get(key)));
}
System.out.println("맵의 크기 : " + hm.size());
맵에 저장된 키들의 집합 : [이십, 삼십, 사십, 십]
키 : 이십, 값 : 20
키 : 삼십, 값 : 30
키 : 사십, 값 : 40
키 : 십, 값 : 10
키 : 이십, 값 : 20
키 : 삼십, 값 : 30
키 : 십, 값 : 10
키 : 이십, 값 : 200
키 : 삼십, 값 : 30
키 : 십, 값 : 10
맵의 크기 : 3
keySet() 메소드는 해당 맵에 포함된 모든 키 값들을 하나의 집합(Set)으로 반환해준다
get() 메소드는 해당 키에 해당하는 value 값을 반환해준다
HashMap과 거의 동일하기 떄문에 거의 사용하지 않는다
키와 값을 한 쌍으로 하는 데이터를 이진 검색 트리(binary search tree)의 형태로 저장
이진 검색 트리이기 떄문에 데이터를 추가하거나 제거하는 등의 기본 동작 시간이 매우 빠름
TreeMap<Integer, String> tm = new TreeMap<Integer, String>();
tm.put(30, "삼십");
tm.put(10, "십");
tm.put(40, "사십");
tm.put(20, "이십");
System.out.println("맵에 저장된 키들의 집합 : " + tm.keySet());
for (Integer key : tm.keySet()) {
System.out.println(String.format("키 : %s, 값 : %s", key, tm.get(key)));
}
tm.remove(40);
Iterator<Integer> keys = tm.keySet().iterator();
while (keys.hasNext()) {
Integer key = keys.next();
System.out.println(String.format("키 : %s, 값 : %s", key, tm.get(key)));
}
tm.replace(20, "twenty");
for (Integer key : tm.keySet()) {
System.out.println(String.format("키 : %s, 값 : %s", key, tm.get(key)));
}
System.out.println("맵의 크기 : " + tm.size());
맵에 저장된 키들의 집합 : [10, 20, 30, 40]
키 : 10, 값 : 십
키 : 20, 값 : 이십
키 : 30, 값 : 삼십
키 : 40, 값 : 사십
키 : 10, 값 : 십
키 : 20, 값 : 이십
키 : 30, 값 : 십
키 : 10, 값 : 십
키 : 20, 값 : twenty
키 : 30, 값 : 삼십
맵의 크기 : 3
TreeMap의 특이한 메소드들은 필요하면 찾아보자