Java를 배워보자

Java에서 두 Map의 공통 요소와 내포된 Map 복사하기

_Blue_Sky_ 2025. 6. 13. 22:14
728x90

 
 
Java에서 두 Map 객체의 공통 키에 해당하는 값을 복사하는 작업은 데이터 처리에서 자주 필요한 기능입니다. 특히, Map 안에 또 다른 Map이 내포되어 있을 경우, 이를 올바르게 복사하려면 깊은 복사를 구현해야 합니다. 이번 포스트에서는 두 Map의 공통 키와 값을 복사하되, 내포된 Map도 재귀적으로 처리하는 방법을 예제와 함께 블로그 형식으로 설명하겠습니다. TypeScript와 비슷한 요구사항을 Java로 구현하는 과정을 단계별로 살펴보겠습니다.

1. 문제 정의
우리의 목표는 다음과 같습니다:
  • Map 객체(sourcetarget)가 있을 때, 공통 키에 해당하는 값만 복사.
  • 값이 또 다른 Map인 경우, 해당 Map의 공통 키와 값을 깊게 복사.
  • Java의 타입 안정성을 유지하면서 동적으로 처리.
예를 들어, 다음과 같은 두 Map이 있다고 가정해 봅시다:
Map<String, Object> source = new HashMap<>();
source.put("name", "John");
source.put("age", 30);
Map<String, String> sourceAddress = new HashMap<>();
sourceAddress.put("city", "Seoul");
sourceAddress.put("country", "Korea");
sourceAddress.put("zip", "12345");
source.put("address", sourceAddress);
source.put("hobby", "reading");

Map<String, Object> target = new HashMap<>();
target.put("name", "");
target.put("age", 0);
Map<String, String> targetAddress = new HashMap<>();
targetAddress.put("city", "");
targetAddress.put("country", "");
targetAddress.put("zip", "");
target.put("address", targetAddress);
target.put("job", "");
여기서 공통 키는 name, age, address입니다. 특히 address는 내포된 Map이므로, 이 Map의 공통 키(city, country)만 복사해야 합니다.

2. 해결 방법
Java에서는 Map의 공통 키를 찾아 값을 복사하는 함수를 작성하고, 내포된 Map을 처리하기 위해 재귀를 사용합니다. Java는 TypeScript처럼 동적 타입을 기본적으로 지원하지 않으므로, Object 타입을 사용하거나 제네릭스를 활용해 타입 안정성을 유지합니다.
2.1. 기본 접근법
먼저, 동적으로 키와 값을 처리할 수 있도록 Map<String, Object>를 사용합니다. 값이 Map인지 확인하고, Map이라면 재귀적으로 복사를 수행합니다.
2.2. 공통 요소 복사 함수
다음은 공통 키를 찾아 값을 복사하는 메서드입니다:
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public class MapUtils {
    public static Map<String, Object> copyMatchingProperties(Map<String, Object> source, Map<String, Object> target) {
        Map<String, Object> result = new HashMap<>();

        // source와 target의 공통 키 찾기
        Set<String> commonKeys = source.keySet().stream()
                .filter(target::containsKey)
                .collect(Collectors.toSet());

        for (String key : commonKeys) {
            Object sourceValue = source.get(key);
            Object targetValue = target.get(key);

            // 값이 Map이고 null이 아닌 경우, 재귀적으로 복사
            if (sourceValue instanceof Map && targetValue instanceof Map) {
                @SuppressWarnings("unchecked")
                Map<String, Object> nestedSource = (Map<String, Object>) sourceValue;
                @SuppressWarnings("unchecked")
                Map<String, Object> nestedTarget = (Map<String, Object>) targetValue;
                result.put(key, copyMatchingProperties(nestedSource, nestedTarget));
            } else {
                // Map이 아닌 경우, source의 값을 복사
                result.put(key, sourceValue);
            }
        }

        return result;
    }
}
 
 
2.3. 함수 동작 원리
  1. 공통 키 찾기: source.keySet().stream().filter(target::containsKey)를 사용해 두 Map에 모두 존재하는 키를 필터링합니다.
  2. 값 처리:
    • 값이 Map 타입인 경우, 해당 값을 Map<String, Object>로 캐스팅한 뒤 재귀적으로 copyMatchingProperties를 호출.
    • Map이 아닌 경우(예: 문자열, 숫자 등), source의 값을 그대로 복사.
  3. 결과 반환: 공통 키와 그 값(또는 재귀적으로 복사된 Map)만 포함된 새로운 Map을 반환.
@SuppressWarnings("unchecked")는 제네릭 캐스팅 경고를 억제하기 위해 사용되었습니다. Java의 런타임 타입 소거로 인해 ObjectMap으로 캐스팅할 때 경고가 발생하기 때문입니다.

3. 예제 사용
이제 위 메서드를 실제 데이터에 적용해 보겠습니다:
import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        // Source Map
        Map<String, Object> source = new HashMap<>();
        source.put("name", "John");
        source.put("age", 30);
        Map<String, String> sourceAddress = new HashMap<>();
        sourceAddress.put("city", "Seoul");
        sourceAddress.put("country", "Korea");
        sourceAddress.put("zip", "12345");
        source.put("address", sourceAddress);
        source.put("hobby", "reading");

        // Target Map
        Map<String, Object> target = new HashMap<>();
        target.put("name", "");
        target.put("age", 0);
        Map<String, String> targetAddress = new HashMap<>();
        targetAddress.put("city", "");
        targetAddress.put("country", "");
        targetAddress.put("zip", "");
        target.put("address", targetAddress);
        target.put("job", "");

        // 공통 요소 복사
        Map<String, Object> result = MapUtils.copyMatchingProperties(source, target);

        // 결과 출력
        System.out.println(result);
    }
}
출력 결과
 
{name=John, age=30, address={city=Seoul, country=Korea}}
결과 분석
  • 공통 키: name, age, address.
  • address 내포된 Map의 공통 키: city, country.
  • sourceziptargetaddress에 없으므로 복사되지 않음.
  • targetjobsource에 없으므로 결과에 포함되지 않음.

 
 
 
4. 제네릭스를 사용한 타입 안정성 강화
위 예제는 Map<String, Object>를 사용해 동적으로 처리했지만, 타입 안정성을 높이기 위해 제네릭스를 활용할 수 있습니다. 예를 들어, Map의 값 타입을 더 구체적으로 제한하고 싶다면 다음과 같이 구현할 수 있습니다:
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public class TypedMapUtils {
    // Address용 Map 타입 정의
    public static class Address extends HashMap<String, String> {}

    // Source와 Target용 Map 타입 정의
    public static class Source extends HashMap<String, Object> {}
    public static class Target extends HashMap<String, Object> {}

    public static Map<String, Object> copyMatchingProperties(Source source, Target target) {
        Map<String, Object> result = new HashMap<>();

        // 공통 키 찾기
        Set<String> commonKeys = source.keySet().stream()
                .filter(target::containsKey)
                .collect(Collectors.toSet());

        for (String key : commonKeys) {
            Object sourceValue = source.get(key);
            Object targetValue = target.get(key);

            // Address 타입의 Map 처리
            if ("address".equals(key) && sourceValue instanceof Map && targetValue instanceof Map) {
                @SuppressWarnings("unchecked")
                Map<String, String> nestedSource = (Map<String, String>) sourceValue;
                @SuppressWarnings("unchecked")
                Map<String, String> nestedTarget = (Map<String, String>) targetValue;
                Map<String, String> nestedResult = new HashMap<>();
                
                // 내포된 Map의 공통 키 복사
                Set<String> nestedCommonKeys = nestedSource.keySet().stream()
                        .filter(nestedTarget::containsKey)
                        .collect(Collectors.toSet());
                
                for (String nestedKey : nestedCommonKeys) {
                    nestedResult.put(nestedKey, nestedSource.get(nestedKey));
                }
                
                result.put(key, nestedResult);
            } else {
                result.put(key, sourceValue);
            }
        }

        return result;
    }
}
이 버전은 address 키의 값이 Map<String, String>임을 명시적으로 처리하며, SourceTarget을 커스텀 클래스로 정의해 타입 안정성을 강화했습니다. 하지만 Java의 제네릭스는 여전히 런타임에서 소거되므로, 캐스팅이 필요할 수 있습니다.

5. 주의사항
  • 깊은 복사: 이 메서드는 공통 키에 대해 깊은 복사를 수행합니다. 하지만 배열, 리스트, 또는 커스텀 객체 등 다른 복잡한 타입은 별도로 처리해야 할 수 있습니다.
  • 성능: 재귀 호출은 Map이 깊게 중첩된 경우 성능에 영향을 줄 수 있으므로, 대규모 데이터에는 주의가 필요합니다.
  • 타입 안정성: Object를 사용하면 유연하지만 타입 오류 위험이 있습니다. 가능하면 제네릭스나 커스텀 클래스를 활용하세요.
  • null 처리: 예제에서는 null 값을 간단히 무시했지만, 실제 프로젝트에서는 null 체크를 더 철저히 해야 할 수 있습니다.

6. 결론
Java에서 두 Map의 공통 요소를 복사하는 작업은 TypeScript와 비슷한 논리를 따르지만, Java의 강한 타입 시스템과 제네릭스 특성상 약간 더 복잡할 수 있습니다. 이 포스트에서 제공한 메서드는 공통 키를 찾아 값을 복사하며, 내포된 Map도 재귀적으로 처리합니다. 동적 처리를 위해 Map<String, Object>를 사용하거나, 타입 안정성을 위해 제네릭스와 커스텀 클래스를 활용할 수 있습니다.
 
728x90