순서가 있는 목록 — List
프로그램을 만들다 보면 여러 값을 한꺼번에 다뤄야 할 때가 많습니다. 학생 이름 다섯 개, 쇼핑 카트에 담긴 상품들, 앱 화면에 보여줄 뉴스 목록. 이런 값들을 변수 하나하나에 담을 수는 없습니다. 그래서 등장하는 것이 List입니다.
Dart의 List는 순서가 있는 값의 모음입니다. 첫 번째, 두 번째, 세 번째처럼 위치가 정해져 있고, 같은 값이 여러 번 들어갈 수 있습니다.
List 만들기 — 세 가지 방법
가장 흔한 방법은 대괄호로 직접 나열하는 리터럴 방식입니다.
void main() {
List<String> fruits = ['사과', '바나나', '딸기'];
print(fruits); // [사과, 바나나, 딸기]
}
List<String>은 "문자열만 담을 수 있는 List"라는 의미입니다. 꺾쇠 안의 타입을 타입 매개변수라고 부릅니다. var를 쓰면 Dart가 타입을 추론해줍니다.
void main() {
var scores = [95, 87, 72, 66, 90];
print(scores); // [95, 87, 72, 66, 90]
}
두 번째 방법은 List.filled입니다. 같은 값으로 채워진 고정 크기 리스트를 만들 때 씁니다.
void main() {
var zeros = List.filled(5, 0);
print(zeros); // [0, 0, 0, 0, 0]
var names = List.filled(3, '이름없음');
print(names); // [이름없음, 이름없음, 이름없음]
}
세 번째는 List.generate입니다. 인덱스를 이용해 값을 계산하면서 리스트를 생성할 때 편합니다.
void main() {
// 0부터 4까지의 제곱수
var squares = List.generate(5, (i) => i * i);
print(squares); // [0, 1, 4, 9, 16]
// 홀수 목록
var odds = List.generate(5, (i) => i * 2 + 1);
print(odds); // [1, 3, 5, 7, 9]
}
(i) => i * i 부분은 인덱스를 받아 값을 반환하는 화살표 함수입니다. PART 03에서 배운 내용이 여기서 바로 써먹힙니다.
인덱스로 접근하기
List의 각 요소는 0부터 시작하는 인덱스로 접근합니다. 첫 번째 요소는 0번, 두 번째는 1번입니다.
void main() {
var fruits = ['사과', '바나나', '딸기', '포도'];
print(fruits[0]); // 사과
print(fruits[2]); // 딸기
// 마지막 요소
print(fruits[fruits.length - 1]); // 포도
print(fruits.last); // 포도 (더 편한 방법)
print(fruits.first); // 사과
// 길이
print(fruits.length); // 4
}
범위를 벗어난 인덱스를 사용하면 런타임 오류가 발생합니다. 인덱스는 항상 0 이상, length - 1 이하여야 합니다.
요소 추가하기 — add와 addAll
리스트에 요소를 추가할 때는 add와 addAll을 씁니다.
void main() {
var fruits = ['사과', '바나나'];
// 하나 추가
fruits.add('딸기');
print(fruits); // [사과, 바나나, 딸기]
// 여러 개 한꺼번에 추가
fruits.addAll(['포도', '멜론']);
print(fruits); // [사과, 바나나, 딸기, 포도, 멜론]
// 특정 위치에 삽입
fruits.insert(1, '복숭아');
print(fruits); // [사과, 복숭아, 바나나, 딸기, 포도, 멜론]
}
insert(index, value)는 지정한 인덱스 앞에 값을 끼워 넣습니다.
요소 삭제하기 — remove와 removeAt
삭제에는 두 가지 방법이 있습니다. 값으로 지울 때는 remove, 위치로 지울 때는 removeAt을 씁니다.
void main() {
var fruits = ['사과', '바나나', '딸기', '포도'];
// 값으로 삭제 (첫 번째로 일치하는 요소 하나 삭제)
fruits.remove('바나나');
print(fruits); // [사과, 딸기, 포도]
// 인덱스로 삭제
fruits.removeAt(0);
print(fruits); // [딸기, 포도]
// 조건에 맞는 것 모두 삭제
var numbers = [1, 2, 3, 4, 5, 6];
numbers.removeWhere((n) => n % 2 == 0); // 짝수 제거
print(numbers); // [1, 3, 5]
// 전체 비우기
var temp = [10, 20, 30];
temp.clear();
print(temp); // []
}
정렬하기 — sort
sort() 메서드는 리스트를 제자리에서 정렬합니다. 새 리스트를 반환하는 것이 아니라 기존 리스트 자체를 바꿉니다.
void main() {
var numbers = [5, 2, 8, 1, 9, 3];
numbers.sort();
print(numbers); // [1, 2, 3, 5, 8, 9]
// 내림차순 정렬
numbers.sort((a, b) => b.compareTo(a));
print(numbers); // [9, 8, 5, 3, 2, 1]
// 문자열 정렬
var names = ['홍길동', '김철수', '이영희'];
names.sort();
print(names); // [김철수, 이영희, 홍길동]
// 길이 순 정렬
var words = ['banana', 'apple', 'kiwi', 'strawberry'];
words.sort((a, b) => a.length.compareTo(b.length));
print(words); // [kiwi, apple, banana, strawberry]
}
sort((a, b) => ...) 형태로 비교 함수를 직접 줄 수 있습니다. 반환값이 음수면 a가 앞, 양수면 b가 앞, 0이면 순서 유지입니다.
검색하기 — contains와 indexOf
리스트 안에 특정 값이 있는지, 어디 있는지 찾는 방법입니다.
void main() {
var fruits = ['사과', '바나나', '딸기', '포도'];
// 포함 여부 확인
print(fruits.contains('딸기')); // true
print(fruits.contains('수박')); // false
// 위치 찾기 (없으면 -1 반환)
print(fruits.indexOf('바나나')); // 1
print(fruits.indexOf('수박')); // -1
// 뒤에서부터 찾기
var nums = [1, 2, 3, 2, 1];
print(nums.lastIndexOf(2)); // 3
}
불변 리스트 — 수정할 수 없는 리스트
때로는 한 번 정한 값이 바뀌지 않기를 바랍니다. 예를 들어 요일 이름이나 설정값처럼요. 이럴 때 불변 리스트를 씁니다.
const를 사용하면 컴파일 시점에 값이 고정됩니다.
void main() {
const days = ['월', '화', '수', '목', '금', '토', '일'];
// days.add('기타'); // 오류! 수정 불가
print(days[0]); // 월
}
런타임에 만들어진 리스트를 불변으로 만들고 싶을 때는 List.unmodifiable을 씁니다.
void main() {
var original = [1, 2, 3];
var fixed = List.unmodifiable(original);
// fixed.add(4); // 오류! 수정 불가
print(fixed); // [1, 2, 3]
// 원본은 바꿀 수 있음
original.add(4);
print(original); // [1, 2, 3, 4]
print(fixed); // [1, 2, 3] — 영향 없음
}
List.unmodifiable은 원본의 복사본을 만들어 잠그는 방식입니다. 원본이 바뀌어도 불변 리스트에는 영향이 없습니다.
자주 쓰는 List 메서드 모아보기
void main() {
var list = [3, 1, 4, 1, 5, 9, 2, 6];
// 비어 있는지 확인
print(list.isEmpty); // false
print(list.isNotEmpty); // true
// 뒤집기 (새 리스트 반환)
print(list.reversed.toList()); // [6, 2, 9, 5, 1, 4, 1, 3]
// 부분 추출 (sublist)
print(list.sublist(2, 5)); // [4, 1, 5]
// 합치기 (join)
var words = ['Hello', 'World', 'Dart'];
print(words.join(', ')); // Hello, World, Dart
// 합계 (fold 사용)
int sum = list.fold(0, (acc, val) => acc + val);
print(sum); // 31
}
실전 예제 — 성적 관리
배운 내용을 활용한 간단한 성적 관리 예제입니다.
void main() {
var scores = [85, 92, 78, 95, 60, 88, 73];
// 통계
scores.sort();
int min = scores.first;
int max = scores.last;
double avg = scores.fold(0, (a, b) => a + b) / scores.length;
print('최저: $min');
print('최고: $max');
print('평균: ${avg.toStringAsFixed(1)}');
// 합격자 (60점 이상)
var passed = scores.where((s) => s >= 60).toList();
print('합격: ${passed.length}명');
// 내림차순으로 순위 출력
scores.sort((a, b) => b.compareTo(a));
for (int i = 0; i < scores.length; i++) {
print('${i + 1}위: ${scores[i]}점');
}
}
출력 결과입니다.
최저: 60
최고: 95
평균: 81.6
합격: 7명
1위: 95점
2위: 92점
3위: 88점
4위: 85점
5위: 78점
6위: 73점
7위: 60점
List 하나로 이 모든 작업을 해냈습니다. 정렬, 검색, 집계까지 모두 List의 내장 기능입니다.
다음 챕터에서는 중복을 허용하지 않는 컬렉션인 Set을 만나봅니다. 같은 값이 두 번 들어오면 자동으로 걸러주는 독특한 녀석입니다.