개발/java

[map] getOrDefault vs computeIfAbsent

방푸린 2023. 12. 20. 15:00
반응형

Map.getOrDefault(key, defaultValue)

key에 해당하는 값이 map에 있으면 그 값을 내리고 없으면 설정한 defaultValue를 내린다.

Map<String, Integer> map = new HashMap<>();
map.put("A", 1);
map.put("B", 2);

int valueA = map.getOrDefault("A", 0); // returns 1
int valueC = map.getOrDefault("C", 0); // returns 0

여기서 주의해야할 점은 map 값의 유무와 상관없이 default value를 생성한다는 것이다.

그래서 값이 있어도 의미없는 객체가 생성되어 메모리를 차지할 수 있다.

따라서 혹시 기본 객체를 반환하려고 한다면(new AAOBJECT()), 매번 생성하는 것보다 하나 생성해놓고 쓰는게 효율적이다.

 

Map.computeIfAbsent(key, mappingFunction)

key에 해당하는 값이 map에 있으면 그 값을 내리고

없거나 null이면 mappingFunction을 실행하여 값을 계산한다.

계산 결과를 다시 map에 저장(put)한다.

getOrDefault 함수의 대안으로 사용하는 경우가 있는데, 불필요한 값이 map에 저장될 수 있어 조심해야 한다.

또한 사용 시 UnsupportedOpertionException 이 발생할 수 있는데, 이는 map이 unmodifiable map 이기 때문이다. hashmap으로 생성된 맵은 괜찮은데 혹시 없을 때 기본 값으로 Collections.emptyMap() 을 선언했다면 해당 맵은 unmodifiable이라 해당 에러가 발생할 수 있다.

Map<String, Integer> map = new HashMap<>();
        
// If "A" is not present, compute a new value using the mapping function
int valueA = map.computeIfAbsent("A", k -> 42);

// If "B" is not present, compute a new value using the mapping function
int valueB = map.computeIfAbsent("B", k -> 100);

System.out.println("Value for A: " + valueA); // Output: Value for A: 42
System.out.println("Value for B: " + valueB); // Output: Value for B: 100
Map<String, List<String>> map = new HashMap<>();
map.computeIfAbsent("A", k -> new ArrayList<>()).add("Apple");
map.computeIfAbsent("B", k -> new ArrayList<>()).add("Banana");

// After the operations, the map will contain {"A"=["Apple"], "B"=["Banana"]}
String[] words = {"Hello", "world", "Java", "programming"};

Map<Integer, StringBuilder> sentenceMap = new HashMap<>();

for (String word : words) {
	sentenceMap.computeIfAbsent(word.length(), k -> new StringBuilder()).append(word).append(" ");
}

sentenceMap.forEach((length, sentence) -> System.out.println("Length " + length + ": " + sentence.toString().trim()));

//Length 4: Java
//Length 5: Hello world
//Length 11: programming
public class FibonacciExample {
    private static Map<Integer, Long> fibCache = new HashMap<>();

    public static void main(String[] args) {
        int n = 10;
        long fibonacci = computeFibonacci(n);
        System.out.println("Fibonacci(" + n + ") = " + fibonacci);
    }

    private static long computeFibonacci(int n) {
        return fibCache.computeIfAbsent(n, k -> (k <= 1) ? k : computeFibonacci(k - 1) + computeFibonacci(k - 2));
    }
}

 

putIfAbsent와 차이점 요약

  1. 값 생성 방식:
    • putIfAbsent: 이미 준비된 값을 Map에 넣기만 함. 값이 항상 미리 준비되어 있어야 함.
    • computeIfAbsent: 값이 필요할 때만 mappingFunction을 호출해 값을 계산하고 삽입함. 값 생성 비용이 클 때 유용함.
  2. 값의 조건적 생성:
    • putIfAbsent: 키가 존재하지 않으면 값을 그대로 추가.
    • computeIfAbsent: 키가 존재하지 않으면 주어진 함수로 값을 계산해 추가.
  3. 사용 용도:
    • putIfAbsent: 단순히 키가 없을 때 특정 값을 추가하고 싶을 때 사용.
    • computeIfAbsent: 값 생성이 비용이 크거나, 키에 따라 동적으로 값을 계산해야 할 때 사용.

성능 및 유용성 측면

  • **putIfAbsent**는 값이 미리 존재하는 경우에 적합하고, 이미 계산된 값을 단순히 Map에 추가할 때 사용됩니다.
  • **computeIfAbsent**는 값의 계산이 복잡하거나 키 기반으로 계산해야 할 경우 적합하며, 이 경우 성능 최적화를 위해 값 계산이 필요한 시점에서만 수행할 수 있어 유리합니다.

 

추가!

concurrentHashMap의 경우 computeIfAbsent는 thread safe 하다

값이 없을 때만 값을 추가하는 연산을 원자적으로 처리해야, 이를 위해 ConcurrentHashMap의 computeIfAbsent() 메서드를 사용하여 해당 연산을 하나의 원자적 작업으로 처리

String value = cache.get("gameSetting");
if (value == null) {
    cache.put("gameSetting", "newValue");
}

cache가 concurrentHashMap이어도 위 코드는 원자성이 부족하여 멀티스레드 환경에 race condition에 놓이게 되어 위험하다.

728x90
반응형