자바 (19). Java Map의 9가지 중요 질
Map의 9가지 중요 질문! : Top 9 questions about Java Maps 번역 및 응용
0. Java Map 이란
일반적으로 Map이란 key와 value의 set로 구성된 자료형이다. 즉 자바의 맵은 대응관계 ( “이름” = “한승우”, “생일” = “0924” ) 등으로 구분할 수 있도록 대응관계를 쉽게 표현할 수 있게 해주는 자료형이다.
Associatve array 또는 Hash라고도 불린다.
이 게시물에선 Java Map의 사용 방법과 구현 클레스에 대한 9가지의 중요 원칙을 다룬다.
단순화를 위하여, 이 곳에선 generic을 사용하며, 그러므로 특정 Map 이 아닌 일반 Map을 사용한다고 한다.
근데 내 포스트에선 그냥 예제를 새로 만들어 확인이 가능하게 만들어 두었다.
1. 원칙 1) Convert a Map to a List
자바 Map 인터페이스는 3가지 collection view 제공한다.
- ket set
- value set
- key-value set 이 3가지 타입 모두 생성자와, addAll 을 사용하여 List로 변환이 가능하다. 아래의 snippet code 는 어떻게 생성자와 addAll() 을 통해 map이 ArrayList 타입이 되는지 보여준다.
public class ex01 {
Map<String, String> map = new HashMap<>();
void addMap() {
map.put("한승우", "0924");
map.put("한승유", "0101");
List keyList = new ArrayList<>(map.keySet());
List valueList = new ArrayList(map.values());
List entryList = new ArrayList(map.entrySet());
List keyList2 = new ArrayList<>();
List valueList2 = new ArrayList();
List entryList2 = new ArrayList();
keyList2.addAll(map.keySet());
valueList2.addAll(map.values());
entryList2.addAll(map.entrySet());
}
}
2. 원칙 2) Iterate over a Map
Iterator 를 이용한 Map
모든 key-value 쌍을 iterating 하는 것은 map을 순회하는 방법중 가장 기본적이다. Java에서 이러한 쌍은 Map.Entry 라는 Map 의 collection view 항목중 하나에 저장됩니다.
Map.entrySet()은 key-value 집합을 리턴하며 아래와 같이 사용된다.
public class ex02 {
void iterMap() {
Map<String, String> map = new HashMap<>();
map.put("한승우", "0924");
map.put("한승유", "0101");
for(Map.Entry entry : map.entrySet()) {
String key = (String) entry.getKey();
String value = (String) entry.getValue();
}
Iterator itr = map.entrySet().iterator();
while (itr.hasNext()) {
Map.Entry entry = (Map.Entry) itr.next();
String key = (String) entry.getKey();
String value = (String) entry.getValue();
}
}
}
3. 원칙 3) Sort a Map on the keys
링크에선 list를 통해 하나만 다루었지만 나는 Map을 정렬하는 모든 방법을 알아보려 한다.
- LinkedHashMap을 이용하여 정렬
- TreeMap을 이용하여 정렬
- List를 이용하여 정렬
- Stream을 이용하여 정렬
이전에 Compartor에 대해 다룬 포스팅이 있으니 잘 모른다면 그걸 먼저 보고 오는게 좋을것 같다.
3-1. LinkedHashMap을 이용하여 정렬
LinkedHashMap은 Map에 입력한 순서가 보장 된다. HashMap을 원하는 순서대로 정렬 하고 이 순서대로 다시 LinkedHashMap에 입력하면 정렬된 순서대로 출력이 가능하다.
key로 정렬 하기 Map.Entry 를 리스트로 가져와 key 값으로 정렬하고, 정렬된 순서대로 LinkedHashMap에 추가 한다.
public class ex03 {
public static void main(String[] args) {
Map<String, String> map = new LinkedHashMap<>();
map.put("4번", "한승우1");
map.put("2번", "한승우2");
map.put("1번", "한승우3");
map.put("3번", "한승우4");
map.put("5번", "한승우5");
Map<String, String> result = sortMapByKey(map);
for (Map.Entry entry : result.entrySet()) {
System.out.println(String.format("%s %s", entry.getKey(), entry.getValue()));
}
}
}
class Ex01 {
public static LinkedHashMap<String, String> sortMapByKey(Map<String, String> map) {
List<Map.Entry<String, String>> entries = new LinkedList<>(map.entrySet());
//Collections.sort(entries, (s1, s2) -> s1.getKey().compareTo(s2.getKey()));
Collections.sort(entries, new Comparator<Map.Entry<String, String>>() {
@Override
public int compare(Map.Entry<String, String> s1, Map.Entry<String, String> s2) {
return s1.getKey().compareTo(s2.getKey());
}
});
LinkedHashMap<String, String> result = new LinkedHashMap<>();
for (Map.Entry<String, String> entry : entries) {
result.put(entry.getKey(), entry.getValue());
}
return result;
}
}
value로 정렬
class Ex02 {
public static LinkedHashMap<String, String> sortMapByValue(Map<String, String> map) {
List<Map.Entry<String, String>> entries = new LinkedList<>(map.entrySet());
Collections.sort(entries, (o1, o2) -> o1.getValue().compareTo(o2.getValue()));
LinkedHashMap<String, String> result = new LinkedHashMap<>();
for (Map.Entry<String, String> entry : entries) {
result.put(entry.getKey(), entry.getValue());
}
return result;
}
}
3-2. TreeMap을 이용한 정렬
TreeMap은 아이템을 추가할 때 __설정한 Comparator__로 정렬되어 저장되도록 구현된 클래스이다. 따라서 값을 추가한 이후에 다시 정렬할 필요가 없다.
class Ex03 {
public static Map<String, String> printTreeMap() {
Comparator<String> comparator = (s1, s2)->s2.compareTo(s1);
Map<String, String> map = new TreeMap<>(comparator);
map.put("4번", "한승우5");
map.put("2번", "한승우3");
map.put("1번", "한승우1");
map.put("3번", "한승우2");
map.put("5번", "한승우4");
return map;
}
}
TreeMap은 value를 정렬하지 않기 때문에 key를 정렬할 때만 사용
3-3 List를 이용한 정렬
Key로 정렬
class Ex04 {
public static Map<String, String> sortByKey(Map<String, String> map) {
List<String> keyList = new ArrayList<>(map.keySet());
keyList.sort((s1, s2)->s1.compareTo(s2));
for (String key : keyList) {
System.out.println("Key: " + key);
}
return map;
}
}
value로 정렬
class Ex04 {
public static Map<String, String> sortByKey(Map<String, String> map) {
Map<String, String> map = new HashMap<>();
map.put("Nepal", "Kathmandu");
map.put("United States", "Washington");
map.put("India", "New Delhi");
map.put("England", "London");
map.put("Australia", "Canberra");
List<String> valueList = new ArrayList<>(map.values());
valueList.sort(String::compareTo);
for (String value : valueList) {
System.out.println("Value: " + value);
}
return map;
}
}
4. 원칙 4) Sort a Map on the values
위 참조 3번 참조
5. 원칙 5) Initialize a static/immutable Map
static / immutable map 의 초기화
Map이 일정하게 유지되기를 원하는 경우, 변경 불가한 Map을 이용하는 것이 좋다. 이러한 방어적인 프로그래밍 기술은 스레드 맵에 대한 안전뿐만 아니라 전체적인 사용에 안전함에 도움이 된다.
static / immutable Map을 초기화 하려면 아래 코드와 같이 사용하면 된다. 문제있는 static map 코드
public class Test {
private static final Map map;
static {
map = new HashMap();
map.put(1, "one");
map.put(2, "two");
}
}
그런데 (위 코드)의 문제가 있다.
Map이 ststic final로 명시 되어있지만서도 초기화 이후에도 map.put(3,”test”) 이 여전히 작동하는 문제가 있다. 그러니 이는 완변히 불변이라 말할 수 없다.
그러므로 static initializer 를 통해 Immutable Map을 만들기 위해서는 우리는 추가적인 익명 Class 와 함께 초기화 마지막 단계에서 변경이 불가한 map을 선언해 주어야 한다. 이제 완료 되었다. 아래의 코드에서 Test.map.put(3,”three”); 을 시도 하려 한다면 UnsupportedOperationException 이 발생할것이다.
public class Test {
private static final Map map;
static {
Map aMap = new HashMap();
aMap.put(1, "one");
aMap.put(2, "two");
map = Collections.unmodifiableMap(aMap);
}
}
6. 원칙 6) Difference between HashMap, TreeMap, and Hashtable
Java에는 3가지 둥요한 Map 인터페이스가 구현되어 있다.
- HashMap
- TreeMap
- Hashtable
이들의 차이점은 아래와 들과 같다.
-
반복의 순서 : HashMap 과 HashTable 은 map에 대한 순서를 보장하지 않는다. 특히 순서가 시간이 지남에 따라 일정하게 유지 된다는 보장이 없다. 그러나 TreeMap은 키의 “natural ording” 또는 comparator을 통해 전부 순회하여 정렬하게 된다.
-
key-value 권 : HashMap 은 null key 와 null value들은 허락한다. (물론 오직 하나의 null key값을 허용한다. 아니면 key중복,,) 반면 HashTable을 key-value 모두 null을 허용하지 않는다. 만약 TreeMap의 natural ordering이나 comparator 가 null을 허용하지 않는 다면 예외를 던진
-
동기화 : Hashtable만 동기화가 된다. 나머지는 동기화는 되지 않는다. 따라서, 안전하게 스레드 구현이 필요 하지 않다면 Hashtable대신 HashMap을 사용하는 것이 좋다.
natural ordering 이
7. 원칙 7) A Map with reverse view/lookup
가끔 우리는 key-key 쌍의 set이 필요 할때가 있다. 즉 map의 value가 key처럼 unique한 값을 갖는 map이 말이다. 이러한 제약은 map의 “inverse lookup/view” 을 통해 만드는 것이 가능하다. 이러한 방법을 통해 value 값을 통해 key 값을 찾을 수 있다. 이러한 데이터 구조를 양방향 Map (bidirectional map) 이라 한다. 하지만 안타깝게도 JDK에선 이 Map을 지원하지 않는다.
8. 원칙 8) Shallow copy of a Map
Map의 얕은 복사
전부는 아니지만 Java에서 대부분의 Map구현은 다른 맵의 복사본 생성자를 제공한다. 그러나 그 절차는 동기화가 되지 않는다. (이게 뭔소리?) 즉 한 스레드가 맵을 복사하면 다른 스레드가 구조적으로 수정할 수 있다.
얕은복사
public class ex04 {
public static void main(String[] args) {
Map origianl = new HashMap();
origianl.put(1, "1");
origianl.put(2, "1");
origianl.put(3, "1");
System.out.println(origianl);
// 동기회되지 않는 상황을 막기 위해 아래 코드와 같이 사용한다고 함
Map copiedMap = Collections.synchronizedMap(origianl);
copiedMap.put(2,"copy");
System.out.println(origianl);
System.out.println(copiedMap);
Map copiedMap2 = origianl;
copiedMap.put(2,"copy2");
System.out.println(origianl);
System.out.println(copiedMap);
System.out.println(copiedMap2);
}
}
깊은 복사
public class ex04 {
public static void main(String[] args) {
HashMap<String, String> origMap = new HashMap<String, String>();
origMap.put("TITLE", "제목");
origMap.put("CONTENT", "내용");
origMap.put("WRITER", "홍길동");
System.out.println(origMap.toString());
// 당연하지만 생성자를 이영한 복사
HashMap<String, String> copyMap = new HashMap<String, String>(origMap);
copyMap.put("WRITER", "홍길동 복사");
System.out.println(copyMap.toString());
System.out.println(origMap.toString());
// clone을 이용한 복사
HashMap<String, String> cloneMap = (HashMap<String, String>)origMap.clone();
cloneMap.put("WRITER", "홍길동 복사222");
System.out.println(cloneMap.toString());
System.out.println(origMap.toString());
// putAll() 메서드 이용한 복사
HashMap<String, String> putMap = new HashMap<String, String>();
putMap.putAll(origMap);
putMap.put("WRITER", "홍길동 복사333");
System.out.println(putMap.toString());
System.out.println(origMap.toString());
}
}
9. 원칙 9) Create an empty Map
빈 맵 생성
만약 맵이 불변이라면 아래와 같이 사용한다.
map = collections.emptyMap();
불변이 아니라면
map = new HashMap();
와 같이 사용한다.