iBetter Books
수정

키와 값의 짝 — Map

사전을 생각해보세요. "apple"이라는 단어를 찾으면 "사과"라는 뜻이 나옵니다. "banana"를 찾으면 "바나나"가 나오고요. 단어와 뜻이 항상 짝을 이루고 있습니다. Dart의 Map이 딱 이런 구조입니다.

Map은 (Key)와 (Value)이 쌍을 이루는 컬렉션입니다. 키를 알면 값을 꺼낼 수 있고, 키는 중복될 수 없습니다. "이름 → 전화번호", "상품코드 → 가격", "설정 이름 → 설정 값"처럼 연결 관계를 표현할 때 딱 맞습니다.

Map 만들기

중괄호 안에 키: 값 형태로 나열합니다.

void main() {
  // Map 리터럴
  Map<String, int> scores = {
    '김철수': 85,
    '이영희': 92,
    '박민준': 78,
  };
  print(scores); // {김철수: 85, 이영희: 92, 박민준: 78}

  // var로 타입 추론
  var config = {
    'host': 'localhost',
    'port': '8080',
    'debug': 'true',
  };
  print(config['host']); // localhost
}

Map<String, int>는 "String 키, int 값을 담는 Map"입니다. 앞이 키 타입, 뒤가 값 타입입니다.

Map.fromIterables — 두 리스트로 Map 만들기

키 목록과 값 목록이 따로 있을 때 유용합니다.

void main() {
  var names = ['김철수', '이영희', '박민준'];
  var scores = [85, 92, 78];

  var scoreMap = Map.fromIterables(names, scores);
  print(scoreMap); // {김철수: 85, 이영희: 92, 박민준: 78}
}

두 리스트의 길이가 같아야 합니다. 길이가 다르면 오류가 발생합니다.

값 접근하기

대괄호에 키를 넣어 값을 꺼냅니다.

void main() {
  var capital = {
    '한국': '서울',
    '일본': '도쿄',
    '미국': '워싱턴 DC',
  };

  // 키로 값 접근
  print(capital['한국']); // 서울

  // 없는 키를 접근하면 null 반환
  print(capital['독일']); // null

  // null 처리
  String? city = capital['독일'];
  print(city ?? '알 수 없음'); // 알 수 없음
}

존재하지 않는 키로 접근하면 오류가 아니라 null을 반환합니다. Null Safety 덕분에 Map<String, String>[] 연산자는 String?을 반환하므로 null 체크가 필요합니다.

[] 대신 putIfAbsent를 쓰면 키가 없을 때만 값을 추가할 수 있습니다.

void main() {
  var counter = <String, int>{};

  // 키가 없으면 기본값 설정
  counter.putIfAbsent('apple', () => 0);
  counter['apple'] = (counter['apple'] ?? 0) + 1;
  print(counter); // {apple: 1}
}

값 수정하기

Map의 값은 언제든지 바꿀 수 있습니다. 키에 새 값을 대입하면 됩니다.

void main() {
  var prices = {'커피': 4500, '케이크': 6000, '주스': 3500};

  // 값 변경
  prices['커피'] = 5000;
  print(prices['커피']); // 5000

  // 새 키 추가
  prices['라떼'] = 5500;
  print(prices); // {커피: 5000, 케이크: 6000, 주스: 3500, 라떼: 5500}

  // update — 기존 값을 기반으로 변경
  prices.update('케이크', (old) => old + 500);
  print(prices['케이크']); // 6500

  // update — 키가 없을 때 기본값 지정
  prices.update('스무디', (old) => old + 100, ifAbsent: () => 4000);
  print(prices['스무디']); // 4000
}

키 삭제하기

void main() {
  var menu = {'커피': 4500, '케이크': 6000, '주스': 3500};

  // 키로 삭제
  menu.remove('주스');
  print(menu); // {커피: 4500, 케이크: 6000}

  // 조건에 맞는 항목 삭제
  menu.removeWhere((key, value) => value > 5000);
  print(menu); // {커피: 4500}

  // 전체 삭제
  menu.clear();
  print(menu.isEmpty); // true
}

containsKey와 containsValue — 포함 확인

void main() {
  var scores = {'김철수': 85, '이영희': 92, '박민준': 78};

  // 키 확인
  print(scores.containsKey('이영희'));  // true
  print(scores.containsKey('최지원')); // false

  // 값 확인
  print(scores.containsValue(92)); // true
  print(scores.containsValue(100)); // false

  // 키가 없으면 기본값
  int score = scores['최지원'] ?? 0;
  print(score); // 0
}

keys, values, entries로 순회하기

Map의 모든 키, 모든 값, 또는 키-값 쌍 전체를 순회할 수 있습니다.

void main() {
  var grades = {
    '수학': 90,
    '영어': 85,
    '과학': 92,
    '국어': 88,
  };

  // 키만 순회
  print('과목 목록:');
  for (String subject in grades.keys) {
    print('  $subject');
  }

  // 값만 순회
  int total = 0;
  for (int score in grades.values) {
    total += score;
  }
  print('총점: $total');
  print('평균: ${total / grades.length}');

  // 키-값 쌍 순회 (entries)
  print('\n성적표:');
  for (var entry in grades.entries) {
    print('  ${entry.key}: ${entry.value}점');
  }
}

출력 결과입니다.

과목 목록:
  수학
  영어
  과학
  국어
총점: 355
평균: 88.75

성적표:
  수학: 90점
  영어: 85점
  과학: 92점
  국어: 88점

entry.keyentry.value로 키와 값을 각각 꺼낼 수 있습니다.

Map 활용 사례

설정(Configuration) 관리

void main() {
  var appConfig = {
    'appName': '나의 앱',
    'version': '1.0.0',
    'theme': 'dark',
    'language': 'ko',
  };

  // 설정 읽기
  String theme = appConfig['theme'] ?? 'light';
  print('테마: $theme'); // 테마: dark

  // 설정 변경
  appConfig['theme'] = 'light';
  print('변경된 테마: ${appConfig['theme']}'); // 변경된 테마: light
}

단어 카운터

void main() {
  String text = 'dart is great dart is fast dart and flutter';
  var words = text.split(' ');

  var wordCount = <String, int>{};
  for (String word in words) {
    wordCount[word] = (wordCount[word] ?? 0) + 1;
  }

  // 정렬하여 출력
  var sorted = wordCount.entries.toList()
    ..sort((a, b) => b.value.compareTo(a.value));

  for (var entry in sorted) {
    print('${entry.key}: ${entry.value}번');
  }
}

출력 결과입니다.

dart: 3번
is: 2번
great: 1번
fast: 1번
and: 1번
flutter: 1번

JSON 스타일 데이터 표현

API 응답이나 사용자 데이터를 Map으로 표현하는 패턴은 Flutter 개발에서 매우 흔합니다.

void main() {
  // 사용자 정보
  var user = {
    'id': 1,
    'name': '홍길동',
    'email': '[email protected]',
    'role': 'admin',
  };

  // 데이터 읽기
  print('이름: ${user['name']}');
  print('이메일: ${user['email']}');

  // Map을 받는 함수
  void printUser(Map<String, dynamic> data) {
    print('---');
    data.forEach((key, value) {
      print('$key: $value');
    });
  }

  printUser(user);
}

Map<String, dynamic>은 값의 타입이 다양할 때 씁니다. JSON 데이터를 파싱하면 보통 이 타입이 됩니다.

다음 챕터에서는 List, Set, Map 모두에 적용할 수 있는 고급 메서드들을 배웁니다. map, where, reduce를 쓰면 반복문 없이도 컬렉션을 자유자재로 다룰 수 있습니다.