Map 계층 구조 소개
컬렉션 프레임워크에서 제공하는 두 번째 주요 구조는 매우 고전적인 데이터 구조인 해시맵 구조의 구현입니다. 이 개념은 새로운 것이 아니며 인메모리 여부에 관계없이 데이터를 구조화하는 데 있어 기본이 됩니다. 어떻게 작동하며 컬렉션 프레임워크에서 어떻게 구현되어 있을까요?
해시맵은 키-값 쌍을 저장할 수 있는 구조입니다. 값은 애플리케이션이 처리해야 하는 모든 객체이며 키는 이 객체를 나타낼 수 있는 것입니다.
인보이스를 처리해야 하는 애플리케이션을 만들어야 하는데, 이 애플리케이션은 Invoice 클래스의 인스턴스로 표현된다고 가정해 보겠습니다. 이때 값은 이러한 Invoice 인스턴스이고 키는 인보이스 번호가 될 수 있습니다. 각 송장에는 번호가 있으며, 이 번호는 모든 송장 간에 고유합니다.
일반적으로 인보이스가 인보이스 번호에 바인딩되는 것처럼 각 값은 키에 바인딩됩니다. 주어진 키가 있으면 해당 값을 검색할 수 있습니다. 일반적으로 키는 여러 문자로 이루어진 문자열이나 숫자와 같은 단순한 객체입니다. 반면에 값은 필요한 만큼 복잡할 수 있습니다. 키를 조작하고, 애플리케이션의 한 부분에서 다른 부분으로 이동하고, 네트워크를 통해 전송하고, 전체 객체가 필요할 때 해당 키로 검색할 수 있는 것이 바로 해시맵이 만들어진 이유입니다.
Map 인터페이스의 모든 세부 사항을 보기 전에 염두에 두어야 할 개념은 다음과 같습니다.
- 해시맵은 키-값 쌍을 저장할 수 있습니다.
- 키는 주어진 값의 심볼 역할을 합니다.
- 키는 단순한 객체이며, 값은 필요에 따라 얼마든지 복잡해질 수 있습니다.
- 키는 해시맵에서 고유하지만 값은 고유하지 않아도 됩니다.
- 해시맵에 저장된 모든 값은 키에 바인딩되어야 하며, 맵의 키-값 쌍은 해당 맵의 엔트리 를 형성합니다.
- 키는 바인딩된 값을 검색하는 데 사용할 수 있습니다.
컬렉션 프레임워크는 다음 그림과 같이 이 개념을 구현하는 Map 인터페이스와 함께 SortedMap 및 NavigableMap 두 가지 확장을 제공합니다.

Map 인터페이스 계층구조
이 계층 구조는 매우 간단하며 SortedSet 및 NavigableSet이 있는 Set 계층 구조처럼 보입니다. 실제로 SortedMap은 키-값 쌍을 키에 따라 정렬된 상태로 유지하는 Map으로, SortedSet과 같은 종류의 시맨틱을 공유합니다. 이 인터페이스가 추가한 메서드는 NavigableMap에서 NavigableSet이 SortedSet에 추가한 것과 같은 종류의 메서드입니다.
JDK는 Map 인터페이스의 여러 구현을 제공하며, 가장 널리 사용되는 것은 HashMap 클래스입니다.
다른 두 가지 구현은 다음과 같습니다.
LinkedHashMap은 키-값 쌍의 순서를 유지하는 내부 구조를 가진HashMap입니다. 키 또는 키-값 쌍을 반복 처리하면 키-값 쌍을 추가한 순서를 따릅니다.IdentityHashMap은 매우 정밀한 경우에만 사용해야 하는 특수한Map입니다. 이 구현은 애플리케이션에서 일반적으로 사용되어서는 안 됩니다. 이 구현은 키 객체를 비교하기 위해equals()및hashCode()를 사용하는 대신, 등호 연산자(==)를 사용하여 이러한 키에 대한 참조만 비교합니다. 꼭 필요한 경우에만 주의해서 사용하세요.
멀티맵에 대해 들어보셨을 것입니다. 멀티맵은 단일 키를 둘 이상의 값에 연결할 수 있는 개념입니다. 이 개념은 컬렉션 프레임워크에서 직접 지원되지 않습니다. 하지만 이 기능은 유용할 수 있으며 이 튜토리얼의 뒷부분에서 실제로는 값의 List인 값으로 Map을 만드는 방법을 살펴보겠습니다. 이 패턴을 사용하면 멀티맵과 유사한 구조를 만들 수 있습니다.
컬렉션에 편리한 팩토리 메서드를 사용하여 Map 만들기
이미 살펴보았듯이 Java SE 9에서는 List 및 Set 인터페이스에 메서드를 추가하여 불변의 목록과 집합을 생성할 수 있습니다.
Map 인터페이스에는 불변의 맵과 불변의 항목을 생성하는 메서드가 있습니다.
다음 패턴을 사용하여 쉽게 Map을 만들 수 있습니다.
Map<Integer, String> map =
Map.of(
1, "one",
2, "two",
3, "three"
);한 가지 주의할 점은 키-값 쌍이 10개 이하인 경우에만 이 패턴을 사용할 수 있다는 것입니다.
그 이상이면 다른 패턴을 사용해야 합니다:
Map.Entry<Integer, String> e1 = Map.entry(1, "one");
Map.Entry<Integer, String> e2 = Map.entry(2, "two");
Map.Entry<Integer, String> e3 = Map.entry(3, "three");
Map<Integer, String> map = Map.ofEntries(e1, e2, e3);이 패턴을 이런 식으로 작성하고 정적 가져오기를 사용하여 가독성을 더욱 향상시킬 수도 있습니다.
Map<Integer, String> map3 =
Map.ofEntries(
Map.entry(1, "one"),
Map.entry(2, "two"),
Map.entry(3, "three")
);이러한 팩토리 메서드로 생성된 Map과 항목에는 Set과 마찬가지로 제한 사항이 있습니다:
- Map 및 항목은 불변 객체입니다.
- 널 항목, 널 키 및 널 값은 허용되지 않습니다.
- 이러한 방식으로 중복 키가 있는 맵을 만들려고 하면 의미가 없으므로 맵 생성 시 경고로
IllegalArgumentException이 발생합니다.
Map에 키/값 쌍 저장하기
키와 바인딩된 값의 관계는 다음 두 가지 간단한 규칙을 따릅니다.
- 키는 하나의 값에만 바인딩할 수 있습니다.
- 값은 여러 키에 바인딩할 수 있습니다.
이로 인해 Map의 콘텐츠에 몇 가지 결과가 발생합니다.
- 모든 키의 집합은 중복을 가질 수 없으므로
Set구조를 갖습니다. - 모든 키/값 쌍의 집합도 중복을 가질 수 없으므로
Set구조를 갖습니다. - 모든 값의 집합은 중복이 있을 수 있으므로 일반
Collection의 구조를 가집니다.
그런 다음 Map에서 다음 작업을 정의할 수 있습니다:
- Map에 키/값 쌍 넣기. 키가 이미 Map에 정의되어 있으면 이 작업이 실패할 수 있습니다.
- 키에서 값 가져오기
- Map에서 키와 해당 값을 제거합니다.
고전적인 Set과 유사한 작업을 정의할 수도 있습니다:
- Map이 비어 있는지 여부 확인
- Map에 포함된 키-값 쌍의 수 가져오기
- 다른 Map의 모든 콘텐츠를 이 Map에 넣기
- Map의 콘텐츠 지우기.
이러한 모든 작업과 개념은 Map 인터페이스에서 구현되며, 다음에서 보게 될 몇 가지 다른 작업과 함께 구현됩니다.
Map 인터페이스 살펴보기
Map 인터페이스는 JDK에서 Map의 개념을 모델링하는 기본 유형입니다.
Map의 키 유형을 선택할 때는 매우 신중해야 합니다. 간단히 말해, 변경 가능한 키를 선택하는 것은 금지되어 있지는 않지만 위험하므로 권장하지 않습니다. 맵에 키를 추가한 후에는 키를 변경하면 해시 코드 값과 신원이 변경될 수 있습니다. 이렇게 되면 키-값 쌍을 복구할 수 없게 되거나 Map을 쿼리할 때 다른 값을 얻을 수 있습니다. 이는 나중에 예시를 통해 확인할 수 있습니다.
Map은 멤버 인터페이스를 정의합니다: 키-값 쌍을 모델링하는 Map.Entry를 정의합니다. 이 인터페이스는 키와 값에 액세스하는 세 가지 방법을 정의합니다:
getKey(): 키를 읽습니다;getValue()및setValue(value): 해당 키에 바인딩된 값을 읽고 업데이트합니다.
주어진 맵에서 얻을 수 있는 Map.Entry 객체는 맵의 콘텐츠에 대한 보기입니다. 따라서 엔트리 객체의 값을 수정하면 맵에 반영되고 그 반대의 경우도 마찬가지입니다. 이 객체의 키를 변경하면 Map이 손상될 수 있으므로 이 객체의 키를 변경할 수 없습니다.