iBetter Books
수정

순서가 있는 목록 — 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

리스트에 요소를 추가할 때는 addaddAll을 씁니다.

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을 만나봅니다. 같은 값이 두 번 들어오면 자동으로 걸러주는 독특한 녀석입니다.