키와 값의 짝 — 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.key와 entry.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를 쓰면 반복문 없이도 컬렉션을 자유자재로 다룰 수 있습니다.