1. 개요

이 사용방법(예제)에서는 중복 키가 있는 맵 , 즉 단일 키에 대해 여러 값을 저장할 수 있는 맵을 처리하는 데 사용할 수 있는 옵션을 살펴보겠습니다 .

2. 표준 Map

Java에는 인터페이스 Map 의 여러 구현이 있으며 각 구현에는 고유한 특성이 있습니다.

그러나 기존 Java 핵심 Map 구현 중 어떤 것도 Map 이 단일 키에 대한 여러 값을 처리하도록 허용하지 않습니다.

보시다시피 동일한 키에 대해 두 개의 값을 삽입하려고 하면 두 번째 값이 저장되고 첫 번째 값은 삭제됩니다.

또한 반환됩니다( put(K 키, V 값) 메서드의 모든 적절한 구현에 의해):

Map<String, String> map = new HashMap<>();
assertThat(map.put("key1", "value1")).isEqualTo(null);
assertThat(map.put("key1", "value2")).isEqualTo("value1");
assertThat(map.get("key1")).isEqualTo("value2");

그렇다면 원하는 행동을 어떻게 달성할 수 있을까요?

3. 가치로서의 수집

당연히 Map 의 모든 값에 대해 Collection을 사용하면 작업을 수행할 수 있습니다.

Map<String, List<String>> map = new HashMap<>();
List<String> list = new ArrayList<>();
map.put("key1", list);
map.get("key1").add("value1");
map.get("key1").add("value2");
 
assertThat(map.get("key1").get(0)).isEqualTo("value1");
assertThat(map.get("key1").get(1)).isEqualTo("value2");

그러나 이 장황한 솔루션에는 여러 가지 단점이 있으며 오류가 발생하기 쉽습니다. 이는 모든 값에 대해 Collection을 인스턴스화하고 , 값을 추가하거나 제거하기 전에 Collection의 존재를 확인하고, 값이 남아 있지 않으면 수동으로 삭제해야 함을 의미합니다.

Java 8부터 compute() 메서드를 활용하고 개선할 수 있습니다.

Map<String, List<String>> map = new HashMap<>();
map.computeIfAbsent("key1", k -> new ArrayList<>()).add("value1");
map.computeIfAbsent("key1", k -> new ArrayList<>()).add("value2");

assertThat(map.get("key1").get(0)).isEqualTo("value1");
assertThat(map.get("key1").get(1)).isEqualTo("value2");

그러나 타사 라이브러리를 사용하지 못하도록 하는 제한적인 회사 정책과 같이 피해야 할 타당한 이유가 없는 한 피해야 합니다.

그렇지 않으면 자체 사용자 지정 Map 구현을 작성하고 휠을 재발명하기 전에 기본 제공되는 여러 옵션 중에서 선택해야 합니다.

4. 아파치 커먼즈 컬렉션

평소와 같이 Apache에는 문제에 대한 솔루션이 있습니다.

Common Collections 의 최신 릴리스 (이제부터는 CC)를 가져오는 것으로 시작하겠습니다 .

<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-collections4</artifactId>
  <version>4.1</version>
</dependency>

4.1. 멀티맵

org.apache.commons.collections4 . MultiMap 인터페이스는 각 키에 대한 값 모음을 보유하는 Map을 정의합니다.

org.apache.commons.collections4.map 에 의해 구현됩니다 . 후드 아래에서 대부분의 상용구를 자동으로 처리하는 MultiValueMap 클래스:

MultiMap<String, String> map = new MultiValueMap<>();
map.put("key1", "value1");
map.put("key1", "value2");
assertThat((Collection<String>) map.get("key1"))
  .contains("value1", "value2");

이 클래스는 CC 3.2 부터 사용할 수 있지만 스레드로부터 안전하지 않으며 CC 4.1에서 더 이상 사용되지 않습니다. 최신 버전으로 업그레이드할 수 없을 때만 사용해야 합니다.

4.2. MultiValuedMap

MultiMap 의 후속 버전은 org.apache.commons.collections4 입니다 . MultiValuedMap 인터페이스. 사용할 준비가 된 여러 구현이 있습니다.

중복을 유지하는 ArrayList 에 여러 값을 저장하는 방법을 살펴보겠습니다 .

MultiValuedMap<String, String> map = new ArrayListValuedHashMap<>();
map.put("key1", "value1");
map.put("key1", "value2");
map.put("key1", "value2");
assertThat((Collection<String>) map.get("key1"))
  .containsExactly("value1", "value2", "value2");

또는 중복 항목을 삭제하는 HashSet 을 사용할 수 있습니다 .

MultiValuedMap<String, String> map = new HashSetValuedHashMap<>();
map.put("key1", "value1");
map.put("key1", "value1");
assertThat((Collection<String>) map.get("key1"))
  .containsExactly("value1");

위 의 구현은 모두 스레드로부터 안전하지 않습니다.

UnmodifiableMultiValuedMap 데코레이터를 사용하여 불변으로 만드는 방법을 살펴보겠습니다 .

@Test(expected = UnsupportedOperationException.class)
public void givenUnmodifiableMultiValuedMap_whenInserting_thenThrowingException() {
    MultiValuedMap<String, String> map = new ArrayListValuedHashMap<>();
    map.put("key1", "value1");
    map.put("key1", "value2");
    MultiValuedMap<String, String> immutableMap =
      MultiMapUtils.unmodifiableMultiValuedMap(map);
    immutableMap.put("key1", "value3");
}

5. 구아바 멀티맵

Guava는 Java API용 Google Core 라이브러리입니다.

프로젝트에서 구아바를 가져오는 것으로 시작하겠습니다.

<dependency>
  <groupId>com.google.guava</groupId>
  <artifactId>guava</artifactId>
  <version>31.0.1-jre</version>
</dependency>

Guava는 처음부터 여러 구현 경로를 따랐습니다.

가장 일반적인 것은 com.google.common.collect입니다. 모든 값에 대해 ArrayList가 지원하는 HashMap을 사용하는 ArrayListMultimap :

Multimap<String, String> map = ArrayListMultimap.create();
map.put("key1", "value2");
map.put("key1", "value1");
assertThat((Collection<String>) map.get("key1"))
  .containsExactly("value2", "value1");

항상 그렇듯이 우리는 Multimap 인터페이스( com.google.common.collect)의 불변 구현을 선호해야 합니다. ImmutableListMultimapcom.google.common.collect. ImmutableSetMultimap .

5.1. 일반적인 Map 구현

특정 Map 구현이 필요할 때 가장 먼저 해야 할 일은 Guava가 이미 구현했을 가능성이 있으므로 존재하는지 확인하는 것입니다.

예를 들어 com.google.common.collect를 사용할 수 있습니다 . 키와 값의 삽입 순서를 유지하는 LinkedHashMultimap :

Multimap<String, String> map = LinkedHashMultimap.create();
map.put("key1", "value3");
map.put("key1", "value1");
map.put("key1", "value2");
assertThat((Collection<String>) map.get("key1"))
  .containsExactly("value3", "value1", "value2");

또는 com.google.common.collect를 사용할 수 있습니다. 키와 값을 자연스러운 순서로 반복하는 TreeMultimap :

Multimap<String, String> map = TreeMultimap.create();
map.put("key1", "value3");
map.put("key1", "value1");
map.put("key1", "value2");
assertThat((Collection<String>) map.get("key1"))
  .containsExactly("value1", "value2", "value3");

5.2. 사용자 정의 MultiMap 위조

다른 많은 구현이 가능합니다.

그러나 아직 구현되지 않은 Map 및/또는 List를 장식하고 싶을 수 있습니다 .

다행스럽게도 구아바에는 Multimap.newMultimap() 이라는 팩토리 메서드가 있습니다 .

6. 결론

기존의 모든 주요 방식으로 의 키에 대한 여러 값을 저장하는 방법을 살펴보았습니다 .

가능한 경우 사용자 지정 솔루션보다 선호되어야 하는 Apache Commons Collections 및 Guava의 가장 인기 있는 구현을 살펴보았습니다.

항상 그렇듯이 전체 소스 코드는 GitHub에서 사용할 수 있습니다 .

res – REST with Spring (eBook) (everywhere)