Java

[Java] Map Interface의 유용한 메서드를 알아보자! (Java 8 기준)

coding-knowjam(코딩노잼) 2021. 4. 7.
728x90

안녕하세요 coding-knowjam입니다.

오늘은 Map Interface의 메서드 중 Java8에서 등장한 유용한 메서드들을 알아보겠습니다.

Java8 하면 빼놓을 수 없는 게 람다식인데 오늘 알아볼 메서드들은 대부분 람다식을 전달 인자로 받습니다.

메서드를 설명드리면서 내부 로직도 같이 살펴볼 텐데 이때 사용한 JDK는 openjdk11입니다.

 

1. compute()

compute() 메서드는 람다식을 통해서 기존의 값에 어떻게 연산을 할지 지정할 수 있습니다.

사용방법은 다음과 같습니다.

Map<String, Integer> map = new HashMap<>();

map.compute("coding", (key, oldValue) -> oldValue == null ? 0 : oldValue + 1);
System.out.println(map.get("coding"));	
// 실행결과 0

// key의 값이 있는게 확실 한 경우에는 아래처럼도 가능
map.compute("coding", (key, oldValue) -> oldValue + 1);
System.out.println(map.get("coding"));	
// 실행결과 1

메서드에 넘겨주는 전달 인자의 첫 번째는 키값이고, 두 번째 인자에 람다식을 전달하면 됩니다.

한 가지 유의할 점은 해당 키의 값이 존재하지 않으면 NullPointerException이 발생하므로, 키의 값이 null일 때 리턴해줄 값도 지정해야 합니다.

위의 예제에서는 삼항 연산자를 이용하였으며 간단하게 설명하면 "coding"이라는 키에 매핑된 value값이 oldValue일 때 oldValue가 null이면 0을 반환하고, 아니라면 oldValue의 값에 +1을 하게 됩니다.

Map을 처음 생성할 때는 아무런 값도 없으므로 "coding"이라는 키의 값이 null일 테니 0을 반환합니다.

그다음에 동일한 람다식을 전달하면 "coding"이라는 키의 값이 0으로 존재하므로 람다식에서의 연산을 통해 0+1이 되고 1을 리턴해줍니다.

 

예제를 통해서 설명을 들어도 처음에는 잘 이해가 되지 않을 수도 있습니다.

그래서 내부 로직을 한번 같이 보겠습니다.

    default V compute(K key,
            BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
        
        // 1.람다식이 존재하는지 null인지 확인
        Objects.requireNonNull(remappingFunction);
        
        // 2. 첫번째 전달인자인 키로 현재 키에 매핑 된 값을 가져옴
        V oldValue = get(key);
	
    	// 3. key와 oldValue의 값을 람다식에 전달 후 새로운 값(newValue) 리턴
        V newValue = remappingFunction.apply(key, oldValue);
        
        // 4. 람다식에서 리턴해준 결과(new Value)가 null인지 아닌지 체크
        if (newValue == null) {
            if (oldValue != null || containsKey(key)) {
                // 5. oldValue가 null이 아닌데 newValue가 null이 나왔다면 key의 값 삭제
                remove(key);
                return null;
            } else {
                return null;
            }
        } else {
			// 6. newValue의 값이 null이 아니면 key의 값으로 넣어주고 newValue를 리턴
            put(key, newValue);
            return newValue;
        }
    }

로직에 대한 설명은 주석으로 달아놓았으니 읽어보시면 될 것 같습니다.

 

2. computeIfAbsent()

이번에 소개할 메서드는 computeIfAbsent()입니다.

메서드 이름에서부터 어떻게 동작할지 느낌이 오는데, 말 그대로 없으면 compute()를 해준다는 의미입니다.

사용방법은 아래와 같습니다.

Map<String, String> map = new HashMap<>();

// 값이 없으면 람다식의 결과 값을 put하고 값 리턴
String value = map.computeIfAbsent("nojam", key -> "Coding" + key );
System.out.println(value);
// 실행결과 : Codingnojam

// 값이 존재하면 해당 key의 값 리턴
value = map.computeIfAbsent("nojam", key -> "Hi" + key );
System.out.println(value);
// 실행결과 : Codingnojam

computeIfAbsent()에 첫 번째 전달 인자는 key이고, 두 번째 전달 인자는 연산을 수행할 람다식을 전달하면 됩니다.

기본적으로 리턴 값은 key의 값이 존재하면 해당 key의 값을 반환해주고, 해당 key의 값이 없으면 람다식을 수행하고 나온 결과 값을 반환해줍니다.

그래서 실제로 위의 로직을 실행해보면 처음에는 "nojam"이라는 key의 값이 없으므로 람다식이 실행되고 Codingnojam이라는 문자열이 결괏값으로 반환되었지만, 두 번째 실행에서는 "nojam"이라는 key의 값이 존재하므로 Hi가 붙은 문자열이 반환되는 게 아니라 해당 key의 값인 Condingnojam이 반환된 걸 알 수 있습니다.

실제로 이 메서드는 trie자료구조를 만들 때 제가 주로 쓰는데 아주 유용합니다.

 

한 번에 이해하시기는 어려울 수도 있으니 내부 로직을 살펴보겠습니다.

    default V computeIfAbsent(K key,
            Function<? super K, ? extends V> mappingFunction) {
        // 1. 람다식이 null인지 체크
        Objects.requireNonNull(mappingFunction);
        V v;
        
        // 2. 파라미터로 받은 key의 value값이 null인지 체크
        if ((v = get(key)) == null) {
            V newValue;
            // 3. 람다식에 key 전달 후 리턴 값이 null인제 체크
            if ((newValue = mappingFunction.apply(key)) != null) {
            	// 4. 해당 key의 value값으로 newValue매핑 후 리턴
                put(key, newValue);
                return newValue;
            }
        }
		
        // 5. key의 value값이 null이 아니라면 해당 key의 value리턴
        return v;
    }

로직에 대한 설명은 주석으로 달아놓았으니 쭉 읽어보시면 될 것 같습니다.

다시 한번 정리하자면 다음과 같습니다.

key에 매핑된 value값이 있으면 기존 value값을 리턴합니다.

key에 매핑된 value값이 없으면 람다식을 실행해서 얻은 value값을 key에 매핑 후 value값을 리턴합니다.

 

3. computeIfPresent()

computIfPresent()는 위에서 살펴본 computIfAbsent()와 반대로 동작한다고 이해하시면 될 것 같습니다.

key에 매핑된 value가 있을 때만 전달받은 람다식을 수행해서 새로운 value를 얻어 매핑 후에 값을 리턴해줍니다.

사용방법은 아래와 같습니다.

Map<String, String> map = new HashMap<>();
map.put("Coding", "NoJam");

// Coding이라는 키에 매핑된 value값 있으므로 람다식을 수행하고 새로운 값을 키에 매핑 후 리턴
String str = map.computeIfPresent("Coding", (key, oldValue) -> key + oldValue + "Hello");
System.out.println(str);	// CodingNoJamHello 출력

// none이라는 키에 매핑된 value값이 없으므로 null 리턴
String str_null = map.computeIfPresent("none", (key, oldValue) -> key + oldValue + "Hello");
System.out.println(str_null); 	// null 출력

첫 번째 인자에는 키를 넘기고, 두 번째 인자에는 실행할 람다식을 전달하면 됩니다.

얼핏 보면 compute()와 비슷해 보이기도 하는데 compute()는 값이 존재하지 않을 경우도 고려해서 람다식을 작성해야 하고, computeIfPresent()는 키에 매핑된 value값이 존재할 때만 수행됩니다.

추가적으로 comput()는 키에 매핑된 value가 없으면 람다식에서 얻은 값으로 매핑을 해주지만, computeIfPresent()는 키에 매핑된 value값이 없으면 null을 리턴합니다.

 

이번에는 내부 로직을 살펴보겠습니다.

default V computeIfPresent(K key, 
			BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
        
        // 1. 람다식이 null인지 체크  
		Objects.requireNonNull(remappingFunction);
        V oldValue;
        
        //2. key에 매핑된 value값이 null인지 아닌지 체크
        if ((oldValue = get(key)) != null) {
            V newValue = remappingFunction.apply(key, oldValue);
            
            // 3. 람다식의 리턴 값이 null인지 아닌지 체크
            if (newValue != null) {
                put(key, newValue);
                return newValue;
            } else {
                remove(key);
                return null;
            }
        } else {
            return null;
        }
    }

위의 내부 로직을 보면 key에 매핑된 value값이 있을 때만 코드가 진행되며, value값이 없을 경우에는 바로 null을 리턴해주고 있습니다.

정리하자면 다음과 같습니다.

key에 매핑된 value값이 존재하면 람다식 실행 후 얻은 값으로 key에 매핑 후 리턴

key에 매핑된 value값이 존재하지 않으면 바로 null을 리턴

 

4. getOrDefault()

getOrDefault() 메서드는 명칭 그대로 key에 매핑된 value값이 존재하면 가져오고, 존재하지 않으면 default값을 가져옵니다.

사용방법은 아래와 같습니다.

Map<String, String> map = new HashMap<>();
map.put("Java", "NoJam");
		
// Java 키에 매핑 된 값이 존재하므로 NoJam 반환       
String str = map.getOrDefault("Java", "Hi");
System.out.println(str); // NoJam 출력
		
// Map이라는 키에 매핑 된 값이 없으므로 default값인 Interface 반환
str = map.getOrDefault("Map", "Interface");
System.out.println(str); //Interface 출력

// HashMap이라는 키에 매핑 된 값이 없으므로 default값인 Interface 반환
str = map.getOrDefault("HashMap", "Interface");
System.out.println(str); //Interface 출력

첫 번째 인자에 key를 주고, 두 번째 인자에 default로 사용할 값을 주면 됩니다.

내용이 너무 단순하기 때문에 내부 로직은 살펴보지 않겠습니다.

 

5. forEach()

forEach()는 Map에서 가지고 있는 key와 value의 값을 하나씩 차례대로 꺼낼 때 사용하면 유용한 메서드입니다.

사용방법은 아래와 같습니다.

Map<String, String> map = new HashMap<>();
map.put("coding", "01");
map.put("Java", "NoJam");
map.put("Coding", "CodingNoJamHello");
map.put("nojam", "Codingnojam");

// 내부에서 key와 value를 쌍으로 하나씩 꺼내서 출력
map.forEach((k, v) -> System.out.println(k + " : " + v));
/* 출력 예시
 * coding : 01
 * Java : NoJam
 * Coding : CodingNoJamHello
 * nojam : Codingnojam 
 */

실제 내부 로직은 단순해서 코드 없이 설명드리겠습니다.

우선 for문이 실행되고 for문 내부에서 Map.Entry객체를 얻어와서 람다식에 넣어서 실행하는 형태입니다.

추가적으로 코드가 궁금하시다면 워낙 단순해서 직접 보시는 걸 권해드립니다.

 

6. putIfAbsent()

마지막으로 살펴볼 메서드는 putIfAbsent()입니다.

이름부터가 명확하죠? key에 매핑된 value값이 없으면(null이면) 값을 put해라!

사용방법은 다음과 같습니다.

Map<String, String> map = new HashMap<>();

// kakao라는 키에 매핑된 value값이 없으므로 very good을 값으로 매핑
map.putIfAbsent("kakao", "very good");
// nvaer라는 키에 매핑된 value값이 없으므로 good good을 값으로 매핑
map.putIfAbsent("naver", "good good");

// kakao 키가 존재하므로 매핑 된 value 값 리턴
String str = map.putIfAbsent("kakao", "bad");
System.out.println(str);  //very good 출력
// naver 키가 존재하므로 매핑 된 value 값 리턴
str = map.putIfAbsent("naver", "not bad");
System.out.println(str);  //good good 출력

첫 번째 인자에 key값을 주고, 두 번째 인자에 매핑하고 싶은 value값을 전달하시면 됩니다.

사용하실 때 유의하실 점은 리턴 값이 put을 했는지 안 했는지에 대한 boolean값이라고 생각하시면 안 됩니다.

실제 리턴 값은 해당 key에 현재 매핑된 value값을 리턴해줍니다.

만약 key에 매핑된 value값이 존재하면 그대로 값을 리턴해주고, 존재하지 않으면 null을 리턴합니다.

 

실제로 그렇게 코드가 짜여있는지 내부 로직을 한번 보겠습니다.

 default V putIfAbsent(K key, V value) {
 		// 1. key에 현재 매핑된 value값을 가져옴
        V v = get(key);
        
        // 2. key에 매핑 된 value값이 없으면 put
        if (v == null) {
            v = put(key, value);
        }
        
        // 3. key에 매핑 된 value값이 존재하면 그대로 value 리턴
        return v;
 }

내부 로직을 보시면 설명드린 것과 일치한 것을 알 수 있습니다.

정리하면 다음과 같습니다.

현재 key에 매핑된 value값이 존재하면 그대로 value값 리턴

현재 key에 매핑된 value값이 존재하지 않으면(null이면), key에 value값 매핑 후 value값 리턴

감사합니다.


728x90

댓글