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와 차이점 요약
- 값 생성 방식:
- putIfAbsent: 이미 준비된 값을 Map에 넣기만 함. 값이 항상 미리 준비되어 있어야 함.
- computeIfAbsent: 값이 필요할 때만 mappingFunction을 호출해 값을 계산하고 삽입함. 값 생성 비용이 클 때 유용함.
- 값의 조건적 생성:
- putIfAbsent: 키가 존재하지 않으면 값을 그대로 추가.
- computeIfAbsent: 키가 존재하지 않으면 주어진 함수로 값을 계산해 추가.
- 사용 용도:
- 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에 놓이게 되어 위험하다.