티스토리 뷰

Backend

자바의 HashMap을 효과적으로 사용하는 법

지마켓 이정근 2021. 2. 26. 15:44

Java의 HashMap에 대하여 딱 이만큼만에서 소개했드시 HashMap은 유용한 자료형이다.

저번시간에는 HashMap의 이론적인 방법을 소개했다면 이번에는 효과적으로 사용하는 방법을 알아보자.


HashMap에서 모든 Key와 Value를 print하기

HashMap에서 모든 Key와 Value를 print하는 다양한 방법이 있다. 하나씩 알아 보자.

  1. 모든 key를 print 하는 방법
Set<String> keys = productPrice.keySet();

//print all the keys  
for (String key : keys) {  
 System.out.println(key);  
}

// or  
keys.forEach(key -> System.out.println(key));

좀 더 간결한 람다 표현식인 forEach를 사용할 수 있다.

  1. 모든 value를 print 하는 방법
Collection<Double> values = productPrice.values();
values.forEach(value -> System.out.println(value));
  1. 모든 key와 value를 같이 print 하는 방법
Set<Map.Entry<String, Double>> entries = productPrice.entrySet();

for (Map.Entry<String, Double> entry : entries) {
  System.out.print("key: "+ entry.getKey());
  System.out.println(", Value: "+ entry.getValue());
}

// or (lambda expression)
productPrice.forEach((key, value) -> {
  System.out.print("key: "+ key);
  System.out.println(", Value: "+ value);
});

Key가 존재하는지 알고 싶은 경우

key가 존재하는지 아닌지 확인하고 싶은 경우가 생긴다.

예를 들어 동적프로그램에서 메모이제이션(Memoization) 은 가장 자주 사용되는 기술 중에 하나이다.

예제를 들어 피보나치 수열은 재귀를 보여주는 경우에 유명한 수열이다. 이 수열의 공식은 아래와 같다.

f(n) = f(n-1) + f(n-2)

그러나 이번 예제에는 같은 값이 계속해서 계산될 것이다. 메모이제이션을 사용한다면 쉽게 예방할 수 있다.

import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;
public class Fibonacci {
    private Map<Integer, BigInteger> memoizeHashMap = new HashMap<>();
    {
        memoizeHashMap.put(0, BigInteger.ZERO);
        memoizeHashMap.put(1, BigInteger.ONE);
        memoizeHashMap.put(2, BigInteger.ONE);
    }
    private BigInteger fibonacci(int n) {
        if (memoizeHashMap.containsKey(n)) {
            return memoizeHashMap.get(n);
        } else {
            BigInteger result = fibonacci(n - 1).add(fibonacci(n - 2));
            memoizeHashMap.put(n, result);
            return result;
        }
    }
    public static void main(String[] args) {
        Fibonacci fibonacci = new Fibonacci();
        for (int i = 0; i < 100; i++) {
            System.out.println(fibonacci.fibonacci(i));
        }
    }
}

코드에서 피보나치 번호가 이미memoizeHashMap에 있는지 여부를 체크한다.

만일 이미 있다면 , 계산할 필요가 없고 단지 key를 가지고 get을 하면 된다. 아닌 경우에는 계산한다.

위의 코드에서는 100 까지의 피보나치 번호를 빠르게 제공한다.

map에 해당 key가 존재한다면 put() 메소드가 value를 key로 대체한다.


개발을 좀 더 쉽게 해주는HashMap에서 사용가능한 메소드 몇개를 살펴보자.

비교: computeIfAbsent() 과 putIfAbsent()

앞서의 코드블록에서finonacci()메소드를 다시 살펴보자.

containsKey()대신에computeIfAbsent()메소드를 사용한다면 좀 더 짧고 간결하게 만들 수 있다.

private BigInteger fibonacci (int n) {
  return memoizeHashMap.computeIfAbsent(n, 
           (key) -> fibonacci(n - 1).add(fibonacci(n - 2)));
}

이 메소드는 두개의 입력값을 받는다. 첫번째는 key이고 두번째는 key를 사용하여 차례대로 value를 반환하는 함수형 인터페이스이다.

map에 key가 있으면 값을 반환하고, 그렇지 않다면, value를 계산하고 map에 추가를 한후에 value를 돌려준다. 이렇게 하면 코드가 보다 간단해지고 짧아진다.

그러나 value를 직접 얻을 수 있는putIfAbsent()라는 다른 메소드도 있다.

productPrice.putIfAbsent("Fish", 4.5);

computeIfAbsent() 와 putIfAbsent() 의 다른 점

computeIfAbsent()는 두번째 파라미터로 만일 키가 없으면 값을 얻기 위하여 호출하는 매핑된 함수를 가지는 반면,putIfAbsent()는 value를 바로 가진다.

computeIfAbsent()는 key를 찾을 수 있다면 함수를 호출하지 않지만,

putIfAbsent() 의 두번째 파라미터로 value를 반환하는 함수를 포함한다면 무조건 함수를 호출한다.

var theKey = "Fish";        

// key가 존재한다면 callExpensiveMethodToFindValue()가 결코 호출되지 않는다. 
productPriceMap.computeIfAbsent(theKey, key -> callExpensiveMethodToFindValue(key));

// key가 존재하여도 callExpensiveMethodToFindValue()가 호출된다.
productPriceMap.putIfAbsent(theKey, callExpensiveMethodToFindValue(theKey)); 

비교 : compute() 와 computeIfPresent()

비슷하게,HashMap은compute()와computeIfPresent()메소드가 있다.

문자열안에 특정한 단어가 몇개가 존재하는지 계산하는 프로그램을 작성해보자.

import java.util.HashMap;
import java.util.Map;
public class WordFrequencyFinder {
    private Map<String, Integer> map = new HashMap<>();
    {
        map.put("Java", 0);
        map.put("Jakarta", 0);
        map.put("Eclipse", 0);
    }
    public void read(String text) {
        for (String word : text.split(" ")) {
            if (map.containsKey(word)) {
                Integer value = map.get(word);
                map.put(word, ++value);
            }
        }
    }
}

위의 코드는 너무 간단하다.

첫째로 key가 map에 이미 존재하는지 확인한다. 존재한다면 value를 자져와서 1증가시킨 value로 업데이트한다.

이것은 구식의 방법이다.

Java 8 은computeIfPresent()라는 멋있는 메소드를 제공한다. 이 메소드로 다시 작성해보자.

public void read(String text) {
  for (String word : text.split(" ")) {
    map.computeIfPresent(word, (String key, Integer value) -> ++value);
  }
}

위의 코드는 보다 멋있어졌다.

computeIfPresent()메소드는 key를 받고 만일 key가 존재하는 경우에만 반복적으로 value 를 계산하는 함수로 다시 매핑한다. 그러므로 다시 매핑한 함수는 map에 오직 key가 존재하는 경우에만 호출되고 그 반에의 경우에는 호출하지 않는다.

그리고computeIfPresent()와 비슷한 입력값을 가지는compute()이라는 다른 메소드가 있다. 그러나 이 메소드는 재매핑된 함수로 계산을 하고 key가 이미 존재하는지 여부에 상관하지 않는다. 다시 매핑된 함수의 출력값을 바탕으로 map에 value를 추가한다.


getOrDefault()

만일 우리가 찾는 key를 가지지 않는 map이 있을 수 있다. 그러나 여전히 value를 가지기 원하고 map이 변경되지 않기를 원하는 경우가 있다. 이런 경우에 getOrDefault()메소드를 사용할 수 있다.

productPriceMap.getOrDefault("Fish", 29.4);

docs.oracle.com/javase/8/docs/api/java/util/Map.html

댓글