du.study기록공간

Java Map Interface (java version 1.2, 8, 9, 10) 본문

자바

Java Map Interface (java version 1.2, 8, 9, 10)

du.study 2019. 11. 24. 00:28
728x90

이번에는 Java Map에 대해서 정리하려 합니다. 예전에 알고리즘 사용할 때, 머리속에서 정리한건 있는데 글을 쓸려하니 뭔가 하나씩 애매.. 해서 이번기회에 기록하려 합니다.

 

현재 자바 진형에서 Map Interface 구현체는 상당히 많이 존재합니다.

C 라고 써있는 것만봐도 많은 HashMap이 있는것을 알 수 있습니다.

정말 다양한 Map이 있지만 결국 이것들은 Map 이라는 Interface를 구현하는 구현체들로 같은 메서드를 제공하기에 각각의 구현체 이전에 간단하게 Map Interface가 제공하는 기능을 정리하려 합니다.

 

해당 구현체들 맨 상위에 있는 Map Interface를 살펴보면 다음과 같습니다.

 

 

java 1.2 부터 지원해주던 기능

int size();

- 해당 map의 size를 리턴해줍니다.

 

boolean isEmpty()

- key-value mapping 이 한개라도 있으면 false, 없으면 true를 리턴합니다.

 

boolean containsKey(Object key)

- 해당 object key값을 통해서 map이 key를 포함한다면 true, 아니면 false를 return합니다.

 

boolean containsValue(Object value)

- 해당 object value값을 통해서 map이 value를 포함한다면 true, 아니면 false를 return합니다.

 

V put(K key, V value)

- key-value mapping 추가합니다. 만약 key가 이미 있다면, 기존의 key가 가지던 값은 새로운 값으로 갱신됩니다.

 

V get(Object key)

- key-value mapping 에서 key에 맞는 값을 가져옵니다. 없으면 null을 return합니다.

 

V remove(Object key)

- key-value mapping 에서 key값에 맞는 mapping을 제거합니다. 만약 key가 없다면 null을 return합니다.

 

void putAll(Map<? extends K, ? extends V> m)

- m이라는 변수에 담긴 key-value mapping를 덮어씁니다. 만약 key를 가지고 있다면 value를 덮어쓰게 됩니다.

 

void clear()

- map에 있는 모든 key-value mapping을 초기화합니다.

 

Set<K> keySet()

- map에 담겨져있는 모든 key값을 Set 으로 가져옵니다. ( 각 구현체별 Set종류 확인필요

 

Set<Map.Entry<K, V>> entrySet();

- 이 메서드를 이용하면 Entry의 Set을 리턴받습니다.

 

Collection<V> values()

- map에 들어있는 모든 value를 반환합니다. 현재 Collection으로 반환하는 이유는 value에서는 중복이 일어날 수 있기에 Colleciton으로 반환됩니다.

 

interface Entry<K, V>

- Entry는 Map 내부의 인터페이스로 내부적으로 저장되는 key,value쌍을 저장하기 위해 사용됩니다. Map인터페이스를 구현시 해당 Entry부분도 구현체에서 구현해줘야합니다.

 

 

Java 8 부터 추가된 기능

여기부터는 조금씩 예제를 추가하겠습니다.

 

getOrDefault

default V getOrDefault(Object key, V defaultValue)

- 해당 Map 에서 get동작을 수행합니다. (((v = get(key)) != null) || containsKey(key)) 만약 이 조건을 충족하지 못한다면 defaultValue를 리턴해줍니다.

 

forEach

default void forEach(BiConsumer<? super K, ? super V> action)

- BiConsumer는 람다식 인터페이스로 해당 부분은 value는 받지만 리턴은 하지않는 함수형 인터페이스로 추후에 람다를 정리하면서( 공부중입니다..) 기록 하겠습니다.

 

해당 함수를 사용하면 forEach 문 처럼 동작하며 Map에서는 첫번째에 key , 두번째에 value 값이 적용이 됩니다.

예제

map.put("key1","hello world");
map.put("key2","hi");
map.forEach((key, value) -> System.out.println("key: " + key + ", value: " + value));

결과
key: key1, value: hello world
key: key2, value: hi

Process finished with exit code 0

replaceAll

default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function)

- 함수에 넘겨주는 값을 이용해서 모든 value값을 변경합니다.

예제

map.put("key1","hello world");
map.put("key2","hi");
map.put("key3","내일이 월요일이라니 ㅠㅠ");

map.replaceAll((key, value) -> "월요일 ㅠㅠ");
map.forEach((key, value) -> System.out.println("key: " + key + ", value: " + value));

//결과

key: key1, value: 월요일 ㅠㅠ
key: key2, value: 월요일 ㅠㅠ
key: key3, value: 월요일 ㅠㅠ

Process finished with exit code 0

putIfAbsent

default V putIfAbsent(K key, V value)

- map에 추가된 key가 없을 경우에 value를 추가해줍니다. 만약 key가 이미 있다면, 기존의 value를 리턴합니다.

Map<String,String> map = new LinkedHashMap<>();
        
System.out.println(map.putIfAbsent("key1","hello world"));
System.out.println(map.putIfAbsent("key1","내일이 월요일이라니 ㅠㅠ"));

//map.putIfAbsent("key1",newFun()); 함수로 추가가 가능

결과
null
hello world

Process finished with exit code 0

remove

default boolean remove(Object key, Object value)

- key 와 value를 비교하여 둘 다 일치하는 mapping값을 제거합니다.

map.put("key1","hello world");
map.put("key2","hi");
map.put("key3","내일이 월요일이라니 ㅠㅠ");

map.remove("key1","hello world"); // 포함된 값
map.remove("key2","error value"); // 엉뚱한 value 값

결과
key: key2, value: hi
key: key3, value: 내일이 월요일이라니 ㅠㅠ

Process finished with exit code 0

replace

default boolean replace(K key, V oldValue, V newValue)

- key 와 oldValue를 map에서 찾아 비교하여 둘 다 일치하는 mapping값을 newValue로 변경해줍니다. 

참고 (Map Interface에 있는 코드)

default boolean replace(K key, V oldValue, V newValue) {
    Object curValue = get(key);
    if (!Objects.equals(curValue, oldValue) ||
        (curValue == null && !containsKey(key))) {
        return false;
    }
    put(key, newValue);
    return true;
}

 

replace

default V replace(K key, V value)

- 해당 key값을 map에서 가지고 있다면, value값으로 변경해줍니다. 만약 key가 존재하지 않는다면 변경은 일어나지 않습니다.

map.put("key1","hello world");
map.put("key2","hi");
map.put("key3","내일이 월요일이라니 ㅠㅠ");

map.replace("key1","비가오는 일요일");
map.replace("key2","자고나면 월요일");
map.replace("key4","없는 key값");

결과
key: key1, value: 비가오는 일요일
key: key2, value: 자고나면 월요일
key: key3, value: 내일이 월요일이라니 ㅠㅠ

Process finished with exit code 0

computeIfAbsent 

default V computeIfAbsent (K key,Function<? super K, ? extends V> mappingFunction)

- map에서 Key값을 비교 후, 만약 map에 key가 없는 경우에만 mappingFunction을 실행하여 newValue를 생성, map에 put,해주게 됩니다. 

 

예제

System.out.println(map.computeIfAbsent("key1",(key) -> computeTest(key)));
System.out.println(map.computeIfAbsent("key1",(key) -> computeTest(key)));
System.out.println(map.computeIfAbsent("key3",(key) -> computeTest(key)));
System.out.println("----------");
       
map.forEach((key, value) -> System.out.println("key: " + key + ", value: " + value));


private static String computeTest(String key){
    return key +" compute Test";
}

//결과
key1 compute Test
key1 compute Test
key3 compute Test
----------
key: key1, value: key1 compute Test
key: key3, value: key3 compute Test

Process finished with exit code 0

 

putIfAbsent 랑 비교해보자면 2가지의 차이점이 있습니다.

1. 초기 key값이 존재하지 않을때, put상황시의 리턴 value

   - computeIfAbsent : 값이 없을경우 map 에 적용시 return값은 newValue( mappingFunction에서 만들어진 값)

   - putIfAbsent : 값이 없을경우 map 에 적용 return값은 null

2. value부분에 대한 함수

   - computeIfAbsent : key가 Map에 없을 경우에만 함수 실행

   - putIfAbsent : key가 Map에 있던 없건 value에 적용된 함수 실행

 

 

computeIfPresent

default V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)

- 아래 작성한 compute와 작업 실행은 동일합니다. 다만!! 선언한 함수를 먼저 사용하지 않고, key값이 map에 포함되어 있는지 확인 후, value가 존재한다면 지정한 함수를 실행합니다.

 

- compute : map에서 key값에 메핑된 value를 가져오고, 우리가 넘겨준 함수 선 실행 후 비교.

- computeIfPresent : map에서 key값에 메핑된 value가 있는것을 확인 후, 함수 실행

 

compute

default V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)

- 해당 메서드는 key을 이용하여 map에서 key에 맞는 value를 가져온 후, 선언한 함수를 먼저 실행합니다.

해당 함수를 통하여 newValue를 생성하고 다음 동작을 수행합니다.

 

newValue가 null 이 아닐때 : key값으로 newValue map에 추가

newValue가 null 일때 : 만약 oldValue가 null이 아니였고, key를 포함하고 있었다면 remove작업 진행

map.compute("key1",(key,value) -> computeTest(key));
map.compute("key2",(key,value) -> computeTest(key));
map.compute("key3",(key,value) -> computeTest(key));
map.compute("key2",(key,value) -> nullFun());
map.forEach((key, value) -> System.out.println("key: " + key + ", value: " + value));

private static String computeTest(String key){
    return key +" compute Test";
}
private static String nullFun(){
    return null;
}

//결과
key: key1, value: key1 compute Test
key: key3, value: key3 compute Test

key1,key2,key3에 각각 put이 되었으나, key2에서 함수를 통해 만들어진 새로운 newValue가 null이여서 key remove진행

 

merge

default V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction)

- 파라미터로 받는 key,value를 먼저 확인하게 되는데, map에 포함되지 않는다면, value를 insert, 만약 존재한다면, 기존 value와 새로운 value를 이용하여 어떻게 처리할지 함수를 이용하여 정리할 수 있습니다. (update,remove 가능)

 

예제

map.put("key1","비오는 일요일");
map.put("key2","hi");
map.put("key3","내일이 월요일이라니 ㅠㅠ");

 map.merge("key1"," 눈물이 나네",(oldValue,newValue)-> oldValue+ newValue);
map.merge("key2","hello world",(v1,v2)-> null);

map.forEach((key, value) -> System.out.println("key: " + key + ", value: " + value));

결과

key: key1, value: 비오는 일요일 눈물이 나네
key: key3, value: 내일이 월요일이라니 ㅠㅠ

Process finished with exit code 0

key1에 대해서는 key1에 "비오는 일요일"이 있었기에, newValue로 들어간 값이 더하는 로직을 타서 결과가 나옵니다.

key2 에 대해서는 임의로 null을 지정했기에  remove가 동작하게 됩니다.

 

 

뭔가 비슷하면서도 살짝식 다른 기능이 많아서 이 기회에 확실하게 파악하고 필요할때 사용해야겠습니다.

 

 

 

그리고 java 9에서는(헉헉 많다..) 크게 2개의 메서드가 추가됩니다.

 

of

- java 9에서는 of 메서드를 통해서 ImmutableCollections 생성 지원을 시작했습니다. 단 9에서는 key,value를 직접 넘겨줘야 합니다.( key,value 10개까지 지원)

 

예제

map = Map.of("key1",computeTest("key1"),"key2",computeTest("key2"));
map.forEach((key, value) -> System.out.println("key: " + key + ", value: " + value));

//map.put("key3","error Test");

결과
key: key2, value: key2 compute Test
key: key1, value: key1 compute Test

Process finished with exit code 0

// 만약 저기서 주석을 풀고 key3를 넣으려하면 UnsupportedOperationException 가 발생되게 됩니다.

 

 

ofEntries

- 해당 메서드는 위에 작성했던of와 유사한 기능으로, Entry를 가변인자로 받아서 Immutable 객체로 생성해주는 역할을 합니다.

 

 

 

마지막으로 알아볼 java 10에서는 다음 Interface가 추가됩니다.

copyOf

static <K, V> Map<K, V> copyOf(Map<? extends K, ? extends V> map)

@SuppressWarnings({"rawtypes","unchecked"})
static <K, V> Map<K, V> copyOf(Map<? extends K, ? extends V> map) {
    if (map instanceof ImmutableCollections.AbstractImmutableMap) {
        return (Map<K,V>)map;
    } else {
        return (Map<K,V>)Map.ofEntries(map.entrySet().toArray(new Entry[0]));
    }
}
map.computeIfAbsent("key1",(key) -> computeTest(key));
map.computeIfAbsent("key1",(key) -> computeTest(key));
map.computeIfAbsent("key3",(key) -> computeTest(key));

map = Map.copyOf(map);
map.forEach((key, value) -> System.out.println("key: " + key + ", value: " + value));

//map.computeIfAbsent("key4",(key) -> computeTest(key));
//map.forEach((key, value) -> System.out.println("key: " + key + ", value: " + value));

//결과
key: key1, value: key1 compute Test
key: key3, value: key3 compute Test
key: key1, value: key1 compute Test

Process finished with exit code 0

해당 ofEntries를 내부적으로 구현하여 map을 넘겨주는 방식으로 Immutable 객체를 생성해주는 메서드가 추가되었습니다.

 

 

Map Interface에 있는 기능정리만 해도 진짜 많기도 하고 정리하면서 람다식 부분에 대해서 제대로 모르는것도 많이 나오네요.. 다음번 정리에서는 각 Map 구현체별 간단한 특징이나 hash 비교 및 추가부분을 기록하려 합니다.

728x90

'자바' 카테고리의 다른 글

Java.util.List Interface(1) ArrayList  (0) 2020.03.25
java.util.LinkedHashMap  (0) 2019.11.26
java.util.HashMap equals, hashcode, resize  (0) 2019.11.24
우선순위 큐 PriorityQueue 사용하기 (heap)  (0) 2019.11.10
Integer Object 127과 128  (0) 2019.08.12
Comments