hanker

Java - Collection Framework의 꽃 Map 사용법 및 활용법 본문

JAVA

Java - Collection Framework의 꽃 Map 사용법 및 활용법

hanker 2025. 2. 17. 00:13
반응형

Java의 Map 인터페이스는 데이터를 키-값 쌍으로 저장하는 자료구조로, 단순한 put/get 외에도 다양한 기능을 제공한다.

이번 글에서는 다양한 활용방법과 동작 방법에 대해서 알아보자.

 


1. Map 이란

 

1-1. Map 개념

Map은 각 데이터가 고유한 키와 그에 대응하는 값으로 구성된다.

한 키에는 하나의 값만 매핑된다.

예를 들어, {"apple" : 1, "banana": 2} 와 같이 구성된다.

- 키는 중복될 수 없다. 

- 값은 중복될 수 있다.

 

1-2. 구현체 종류

Java에서는 여러 Map 구현체를 제공한다.

- HashMap : 해시 테이블을 기반으로 하며, 순서 보장이 없다.

- TreeMap : 키를 정렬된 순서에 따라 저장한다.

- LinkedHashMap : 삽입 순서 또는 접근 순서를 유지한다.

- ConcurrentHashMap : 멀티스레드 환경에서 안전하게 사용할 수 있다.


2. 기본 메서드

 

Map 인터페이스는 데이터를 추가, 조회, 삭제하기 위한 기본 메서드를 제공한다.

import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        // 1. Map 생성 및 데이터 추가 (put)
        Map<String, Integer> map = new HashMap<>();
        map.put("apple", 10);
        map.put("banana", 20);
        map.put("cherry", 30);
        System.out.println("초기 Map: " + map); // 예: {apple=10, banana=20, cherry=30}

        // 2. 값 조회 (get)
        // "apple" 키에 해당하는 값을 반환
        Integer appleCount = map.get("apple");
        System.out.println("apple의 값: " + appleCount); // 출력: 10

        // 3. 키 존재 여부 확인 (containsKey)
        if (map.containsKey("banana")) {
            System.out.println("Map에 'banana' 키가 존재합니다.");
        }

        // 4. 값 존재 여부 확인 (containsValue)
        if (map.containsValue(30)) {
            System.out.println("Map에 값 30이 존재합니다.");
        }

        // 5. 엔트리 삭제 (remove)
        // "cherry" 키에 해당하는 엔트리를 제거
        map.remove("cherry");
        System.out.println("cherry 제거 후 Map: " + map); // 예: {apple=10, banana=20}

        // 6. keySet(), values(), entrySet() 사용
        // 모든 키를 반환
        System.out.println("모든 키: " + map.keySet()); // 예: [apple, banana]

        // 모든 값을 반환
        System.out.println("모든 값: " + map.values()); // 예: [10, 20]

        // 모든 키-값 쌍을 엔트리 형태로 반환
        System.out.println("모든 엔트리: " + map.entrySet()); // 예: [apple=10, banana=20]
    }
}

- put : 키와 값을 추가한다. 중복 X

- get : 키에 해당하는 값을 반환하며, 키가 없으면 null을 반환한다.

- remove : 해당 키의 엔트리를 삭제한다.

- containsKey / containsValue특정 키 또는 특정 값의 존재 여부를 확인한다.

- keySet / values / entrySet : Map에 저장된 모든키, 값 또는 키-값 쌍을 각각의 컬렉션으로 반환한다.

 


3. Java 8 이후 추가된 고급 기능과 예제

 

Java 8 부터 람다 표현식을 활용해 Map을 더욱 효율적으로 다룰 수 있도록 여러 메서드가 추가되었다.

 

3-1. putIfAbsent

지정한 키가 없을 경우에만 값을 넣어준다. (중복 확인)

import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("apple", 1);

        // "apple" 키가 이미 존재하므로 값은 변경되지 않음
        map.putIfAbsent("apple", 2);
        System.out.println("apple: " + map.get("apple"));  // 출력: 1
    }
}

 

 

3-2. compute

키의 존재 여부와 상관없이 람다식을 통해 값을 계산하여 저장한다.

예를 들어, 기존 값에 10을 더하거나, 값이 없으면 초기값을 할당한다.

import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("apple", 1);

        // 기존 값 1에 10을 더해서 업데이트
        map.compute("apple", (key, val) -> (val == null) ? 1 : val + 10);
        System.out.println("apple after compute: " + map.get("apple"));  // 출력: 11

        // "banana"는 키가 없으므로 20을 할당
        map.compute("banana", (key, val) -> (val == null) ? 20 : val + 10);
        System.out.println("banana: " + map.get("banana"));  // 출력: 20
    }
}

 

 

3-3. computeIfAbsent

키가 존재하지 않을 대에만 람다식을 통해 값을 계산하고 추가한다.

주로 캐싱(lazy initialization)에 활용된다.

import java.util.HashMap;
import java.util.Map;

public class CachingExample {
    public static void main(String[] args) {
        Map<Integer, String> cache = new HashMap<>();

        // 키 1에 해당하는 데이터가 없을 때만 람다식 실행
        String data = cache.computeIfAbsent(1, id -> {
            System.out.println("데이터 생성 중...");
            return "데이터 for id " + id;
        });
        System.out.println(data);
        
        // 이미 캐싱되어 있으므로, 람다식이 다시 실행되지 않음
        String cachedData = cache.computeIfAbsent(1, id -> {
            System.out.println("이 메시지는 출력되지 않습니다.");
            return "새로운 데이터";
        });
        System.out.println(cachedData);
    }
}

 

 

3-4. computeIfPresent

키가 존재할 때만 값을 업데이트한다.

특정 조건을 만족하는 경우에만 값 변경을 할 경우 유용하다.

import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> inventory = new HashMap<>();
        inventory.put("apple", 10);
        inventory.put("banana", 5);

        // "apple" 키가 존재할 때만 재고를 2배로 증가
        inventory.computeIfPresent("apple", (key, val) -> val * 2);

        // "orange" 키는 없으므로 아무 변화 없음
        inventory.computeIfPresent("orange", (key, val) -> val * 2);

        inventory.forEach((key, value) ->
                System.out.println(key + " 재고: " + value)
        );
    }
}

 

 

3-5. merge

키가 없으면 주어진 값을 저장하고, 키가 있으면 기존 값과 새 값을 결합(merge)하는 방식으로 처리한다.

예를 들어, 단어 빈도수 카운팅이나 누적 합계를 구할 때 유용하다.

import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        String[] words = {"apple", "banana", "apple", "orange", "banana", "apple"};
        Map<String, Integer> wordCount = new HashMap<>();

        for (String word : words) {
            // 키가 없으면 1을, 있으면 기존 값에 1을 더함
            wordCount.merge(word, 1, Integer::sum);
        }

        wordCount.forEach((key, count) ->
                System.out.println(key + " : " + count)
        );
    }
}

 


정리

 

Map 인터페이스는 기본 메서드와 추가된 고급 메서드를 통해 단순한 데이터 추가 및 삭제와 복잡한 로직을 간결하게 처리할 수 있다.

각 메서드를 적절히 활용하면 코드의 가독성과 유지보수성이 크게 향상된다.

 

끝.

반응형