컬렉션 프레임워크의 배경 지식은 이전 포스트를 참고하시길 바랍니다.
목차
<Map 인터페이스>
- 'Map 인터페이스'는 자료를 쌍(Pair)으로 관리하는 데 필요한 메서드가 정의되어 있다.
- key-value 쌍으로 이루어진 객체의 key 값은 유일하며 value 값은 중복될 수 있다.
- Map 인터페이스를 구현한 클래스는 내부적으로 해시 알고리즘에 의해 구현되어있다.
우선 가장 많이 사용하는 'HashMap 클래스'부터 보자.
<HashMap 클래스>
'HashMap'은 Map 인터페이스를 구현한 클래스 중 가장 많이 사용한다.
HashMap에서 자료를 관리하는 방식이 해시 방식이다.
해시 방식의 자료를 저장하는 공간을 해시 테이블이라고 부른다.
Key값이 정해지면 그에 대응하는 해시 테이블의 저장위치가 정해지는데, 이런 위치를 계산하는 함수가 '해시 함수'.
아래 처럼 해시 함수를 표현할 수 있다.
index = hash(key) //index는 저장 위치
새로운 key-value 자료가 입력되거나, key를 알고있는 상태에서 value를 검색하는 데 걸리는 시간은 산술적으로 계산할 수 있다. 그러므로 자료 추가속도나 검색속도가 상당히 빠르다는 특징이 있다.
해시 함수를 어떻게 만드느냐는 key 값 특성이나 개발 프로그램의 성격에 따라 다를 수 있다.
그런데 서로 다른 key값에 같은 index가 반환되는 충돌(collision)이 발생하는 경우도 있다. 따라서 해시 테이블에 데이터를 꽉 채우지 않고 적정 수준이 되면 테이블을 확장해 충돌 발생 확률을 낮춰야 한다.
또한 Map 인터페이스에서 사용하는 key 값은 중복이 불가하므로 equals(), hashCode() 메서드를 재정의하는 것이 좋다.
※자바는 해시 테이블의 75%까지만 사용하고 컴파일러가 자동으로 메모리를 확장한다.
HashMap 활용
HashMap을 활용하여 회원 관리 프로그램을 만들어보자.
key 값은 회원의 ID, value는 회원 클래스로 구현한다. 컬렉션에서 사용한 Member 클래스를 그대로 사용하고 관리 클래스와 테스트 클래스를 구현해보도록 하자.
package collection.hashmap;
import java.util.HashMap;
import java.util.Iterator;
import collection.Member;
public class MemberHashMap {
private HashMap<Integer, Member> hashMap;
public MemberHashMap() {
hashMap = new HashMap<Integer, Member>();
}
//HashMap에 회원을 추가하는 메서드
public void addMember(Member member) {
hashMap.put(member.getMemberId(), member); //key-value 쌍으로 추가
}
//HashMap에서 회원을 삭제하는 메서드
public boolean removeMember(int memberId) {
if(hashMap.containsKey(memberId)) { //HashMap에 매개변수로 받은 키 값인 회원 Id가 있다면
hashMap.remove(memberId); //해당 회원 삭제
return true;
}
System.out.println(memberId + "가 존재하지 않습니다.");
return false;
}
//Iterator를 사용해 전체 회원을 출력하는 메서드
public void showAllMember() {
Iterator<Integer> ir = hashMap.keySet().iterator();
while (ir.hasNext()) { //다음 key가 있으면
int key = ir.next(); //key 값을 가져와서
Member member = hashMap.get(key); //key로부터 value 가져오기
System.out.println(member);
}
System.out.println();
}
}
- key값은 회원 Id, value는 회원 클래스이다.
- HashMap을 선언하고 생성자에서 생성했다.
- addMember() 메서드에서 회원 ID(key)와 회원 클래스(value)를 'put() 메서드를 사용해 추가'한다.
- removeMember() 메서드를 보면, 매개변수로 key값인 memberId가 전달되었다. 우선 'containsKey() 메서드'를 호출해 해당 key 값이 HashMap에 존재하는지 확인하고 존재할 경우 key 값을 사용해 삭제한다.
- showAllMember() 메서드는 전체 회원을 출력하는 메서드다.
Map 인터페이스는 모든 자료를 한 번에 순회할 수 있는 방법이 없다.
모든 자료를 순회하려면 key값을 우선 가져와 key값에 해당하는 value를 찾아야 한다.
- 'hashMap.keySet() 메서드'를 호출하면 모든 key값이 Set 객체로 반환된다.
반환된 Set 객체에 iterator() 메서드를 호출하면 key를 순회할 수 있는 Iterator가 반환된다. 그리고 모든 key값을 하나씩 순회하면서 get()메서드를 사용해 해당 value값을 가져온다. - 이 외에 'HashMap의 values()메서드' 를 사용하면 key값 없이 모든 value값을 Collection 자료형으로 반환해준다.
key는 중복될 수 없으므로 반환형이 Set이고, value는 중복 가능하므로 Collection이 된다.
key값으로 쓰인 회원 Id는 Integer형이다.
Integer클래스는 equals() 메서드와 hashCode()메서드가 이미 재정의 되어있다. 테스트 코드를 구현해보자.
package collection.hashmap;
import collection.Member;
public class MemberHashMapTest {
public static void main(String[] args) {
MemberHashMap memberHashMap = new MemberHashMap();
Member memberLee = new Member(1001, "이지원");
Member memberSon = new Member(1002, "손민국");
Member memberPark = new Member(1003, "박서훤");
Member memberHong = new Member(1004, "홍길동");
memberHashMap.addMember(memberLee);
memberHashMap.addMember(memberSon);
memberHashMap.addMember(memberPark);
memberHashMap.addMember(memberHong);
memberHashMap.showAllMember();
//회원 Id(key값)가 1004인 회원 삭제
memberHashMap.removeMember(1004);
memberHashMap.showAllMember();
}
}
모든회원이 잘 추가되었고, 회원 Id가 1004인 홍길동 회원이 삭제된 모습을 확인할 수 있다.
이처럼 쌍으로 된 자료는 HashMap을 사용하여 관리하면 편리하다.
HashMap과 HashTable
HashMap과 Hashtable 클래스는 모두 쌍으로 이루어진 자료를 관리하는 데 사용된다.
'Hashtable 클래스'는 자바 1부터 사용되었고 Vector 클래스와 마찬가지로 멀티스레드를 위한 동기화를 제공하기 때문에 멀티스레드 환경이 아니라면 HashMap을 사용하는 것을 권장한다.
<TreeMap 클래스>
'Map 인터페이스'를 구현한 클래스 중 key값으로 자료를 정렬하려면 'TreeMap'을 사용할 수 있다.
TreeMap은 TreeSet과 마찬가지로 이진 검색 트리로 구현되어있다. key값으로 정렬하므로 key 값에 해당하는 클래스에 Comparable이나 Comparator 인터페이스를 구현해야 한다.
회원 관리 프로그램에서 사용하는 key값인 회원 Id는 Integer형이다. JavaDoc에서 Integer클래스를 보면 이미 Comparable 인터페이스가 구현되어 있다.
따라서 이 예제에서는 따로 Comparable 인터페이스를 구현하지 않아도 된다.
이제 관리 클래스와 테스트 클래스를 구현해보자.
TreeMap 활용
MemberTreeMap 코드 작성
package map.treemap;
import java.util.Iterator;
import java.util.TreeMap;
import collection.Member;
public class MemberTreeMap {
private TreeMap<Integer, Member> treeMap;
public MemberTreeMap() {
treeMap = new TreeMap<Integer, Member>();
}
//key-value쌍으로 추가
public void addMember(Member member) {
treeMap.put(member.getMemberId(), member);
}
public boolean removeMember(int memberId) {
if(treeMap.containsKey(memberId)) {
treeMap.remove(memberId); //key값에 해당하는 자료 삭제
return true;
}
System.out.println(memberId + "는 존재하지 않습니다");
return false;
}
public void showAllMember() {
Iterator<Integer> ir = treeMap.keySet().iterator();
while(ir.hasNext()) {
int key = ir.next();
Member member = treeMap.get(key);
System.out.println(member);
}
System.out.println();
}
}
MemberTreeMapTest 코드 작성
package map.treemap;
import collection.Member;
public class MemberTreeMapTest {
public static void main(String[] args) {
MemberTreeMap memberTreeMap = new MemberTreeMap();
//회원 Id 순서와 상관없이 회원 추가
Member memberPark = new Member(1003, "박서훤");
Member memberLee = new Member(1001, "이지원");
Member memberHong = new Member(1004, "홍길동");
Member memberSon = new Member(1002, "손민국");
memberTreeMap.addMember(memberPark);
memberTreeMap.addMember(memberLee);
memberTreeMap.addMember(memberHong);
memberTreeMap.addMember(memberSon);
memberTreeMap.showAllMember();
memberTreeMap.removeMember(1002);
memberTreeMap.showAllMember();
}
}
회원 추가와 삭제도 잘 이루어져 있다.
추가되는 순서와 상관없이 key값인 회원 Id를 기준으로 잘 정렬된다.
자바의 컬렉션 프레임워크는 자료 구조를 최적화하여 구현되어있고, 다양한 메서드도 구현되어있다.
이러한 클래스의 특성을 잘 이해해 두면 직접 프로그램을 만들 때 적절하게 활용할 수 있다.