춘기IT/춘기개발

Dart 심화 문법(1) - 컬렉션(Collections)

화춘기 2025. 6. 27. 13:07

Today I Learned (250627) 2.

 

컬렉션(Collections)

여러 개의 값을 그룹으로 묶어서 효율적으로 관리할 수 있는 도구

 

컬렉션의 종류

  • List
  • Set
  • Map

1. List

- 순서가 있는 값들이 묶인 형태

- 배열이라고도 불림

  • List<[타입]> [변수이름] = [요소];
// List<[타입]> [변수 이름] = [요소];

List<int> numbers = [1, 2, 3, 4, 5];

// 각각의 요소는 ,로 구분
// 요소 없는 List 만들 수 있음

List<int> numbers = [];

 

  • [변수이름] = [요소];
var numbers = [1, 2, 3, 4, 5];

//List 요소에 변동이 있는 경우에는 var 로 선언
//이 때 numbers를 List<int> 타입으로 추론함
final numbers = [1, 2, 3, 4, 5];
const names = ['Alice', 'Bob', 'Henry'];

/*
List 요소에 변동이 없는 경우에는 final이나 const로 선언 가능
numbers를 List<int> 타입으로 추론하고
names를 List<String> 타입으로 추론함
*/
//요소 없는 List 만들 수 있음

var numbers = [];
var names = <String>[];

 

 

List 특징

1) 각각의 요소는 모두 같은 타입이어야 함

    ㄴ 하나라도 다른 타입인 요소가 포함되면 오류 발생

List<String> fruits = ['사과', '파인애플', '오렌지', 8];

// Error: A value of type 'int' can't be assigned to a variable of type 'String'.
// fruits의 요소는 String 타입이어야 하는데 8이 들어가서 오류

 

* 참고) 타입이 다른 요소를 넣었을 때 오류가 발생하지 않는 경우

/* List를 선언할 때 요소의 타입을 정해 주지 않은 경우에는 
그 List 안에 있는 요소들의 타입이 서로 달라도 됨 */

const fruits = ['사과', '파인애플', '오렌지', 8];
print(fruits.runtimeType); // List<Object>

//String과 int의 상위 클래스인 Object로 추론
//runtimeType: 객체 타입 반환

 

2) List: Index를 통해 각 요소에 접근할 수 있음

List<int> numbers = [1, 2, 3, 4, 5];
print(numbers[0]); // 1

/* Index는 0 이상의 정수이고, 첫번째 요소의 Index는 0
첫번째 요소의 Index가 1 이 아니라는 것에 주의 */
//참고) Index에 음수를 넣으면 RangeError 발생

List<int> numbers = [1, 2, 3, 4, 5];
print(numbers[-1]); // RangeError (index): Index out of range: index must not be negative: -1

 

3) Index를 통해 요소를 바꿀 수도 있음

var numbers = [1, 2, 3, 4, 5];
print(numbers[3]); // 4

numbers[3] = 2;
print(numbers[3]); // 2

 

ㄴ 만약에 여기에서 List를 상수로 선언해도 요소를 바꿀 수 있나? X

//상수로 선언한 List의 요소를 바꾸려고 하면 오류 발생

const numbers = [1, 2, 3, 4, 5];
print(numbers[3]); // 4

numbers[3] = 2;
print(numbers[3]); // Unsupported operation: indexed set

 

4) length를 통해 요소의 개수를 구할 수 있음

var fruits = ['사과', '파인애플', '오렌지'];
print(fruits.length); // 3
print(fruits[fruits.length - 1]); // 오렌지

/* fruits.length는 3
인덱스는 0, 1, 2 (0부터 시작하니까 마지막 요소는 2번 인덱스)
fruits[3 - 1] = fruits[2] → '오렌지' 출력

print(fruits[fruits.length - 1]);는 리스트의 마지막 요소를 출력하는 코드
length는 3이고, 인덱스는 0부터 시작하므로 length - 1인 2번째 인덱스는 '오렌지' */

 

5-1) isEmpty를 통해 List에 요소가 없는지(List가 비어있는지) 알 수 있음

var numbers = [];
print(numbers.isEmpty); // true
print(numbers.length); // 0

var fruits = ['사과', '파인애플', '오렌지'];
print(fruits.isEmpty); // false

//요소가 없으면 true, 요소가 하나라도 있으면 false 반환

 

5-2) isNotEmpty: List에 요소가 있는지 판별

var numbers = [];
print(numbers.isNotEmpty); // false

var fruits = ['사과', '파인애플', '오렌지'];
print(fruits.isNotEmpty); // true

//요소가 하나라도 있으면 true, 요소가 없으면 false 반환

 

6) indexOf()를 통해 특정 요소의 Index를 알 수 있음

var fruits = ['사과', '파인애플', '오렌지'];
print(fruits.indexOf('파인애플')); // 1
print(fruits.indexOf('포도')); // -1

//리스트에 없는 요소라면 -1을 반환함

 

7) add(), addAll()을 통해 요소를 추가할 수 있음

var fruits = ['사과', '파인애플', '오렌지'];
fruits.add('복숭아');
print(fruits[3]); // 복숭아

fruits.addAll(['포도', '귤']);
print(fruits); // [사과, 파인애플, 오렌지, 복숭아, 포도, 귤]

final additionalFruits = ['샤인머스켓', '수박'];
fruits.addAll(additionalFruits);
print(fruits); // [사과, 파인애플, 오렌지, 복숭아, 포도, 귤, 샤인머스켓, 수박]

//add()는 요소 하나만 추가할 때, addAll()은 여러 요소를 추가할 때 사용

/* additional과 addAll() 차이
additionalFruits는 그냥 리스트 변수
addAll()은 fruits라는 리스트에 다른 리스트의 내용을 붙이는 작업(메서드)이고,
addAll(additionalFruits)는 '샤인머스켓', '수박'을 fruits 리스트에 한꺼번에 추가하는 행동

 

8) remove(), removeAt()을 통해 요소 삭제 가능

var fruits = ['사과', '파인애플', '오렌지'];
fruits.remove('파인애플');
print(fruits); // [사과, 오렌지]

var appleIndex = fruits.indexOf('사과');
fruits.removeAt(appleIndex);
print(fruits); // [오렌지]

/*
remove()는 삭제할 요소를 ()안에 넣으면 되고,
removeAt()은 삭제할 요소의 Index를 ()안에 넣으면 됨
*/

 

9) clear()를 통해 모든 요소 삭제 가능

var fruits = ['사과', '파인애플', '오렌지'];
fruits.clear();
print(fruits); // []
print(fruits.isEmpty); // true
print(fruits.isNotEmpty); // false

2. Set

- 중복되지 않은 값들이 묶인 형태 > 수학의 집합과 같음

  • Set<타입> [변수이름] = {요소};
Set<int> numbers = {1, 2, 3, 4, 5};

//각각의 요소는 ,로 구분
//요소 없는 Set도 만들 수 있음

Set<int> numbers = {};

 

  • 변수 이름 = {요소};
var numbers = {1, 2, 3, 4, 5};

/* Set 요소에 변동이 있는 경우에는 var로 선언
이 때 numbers를 Set<int> 타입으로 추론함 */
final numbers = {1, 2, 3, 4, 5};
const names = {'Alice', 'Bob', 'Henry'};

/* Set 요소에 변동이 없는 경우에는 final이나 const로 선언 가능
numbers를 Set<int> 타입으로 추론하고,
names를 Set<String> 타입으로 추론 */
//요소 없는 Set 만들 수 있음

var names = <String>{};

Q. List 처럼 var numbers = {}; 형태는 안되나? 안됨!

- 저 형태로 선언하면 numbers는 Map 타입이 됨..

var names = {};
print(names.runtimeType); // LinkedMap<dynamic, dynamic>

Set 특징

1) 각각의 요소는 모두 같은 타입이어야 함

Set<String> fruits = {'사과', '파인애플', '오렌지', 8};
// Error: A value of type 'int' can't be assigned to a variable of type 'String'.

//fruits의 요소는 String 타입이어야하는데, 8은 int 타입으로 오류 발생

 

* 참고) List와 마찬가지로 타입이 다른 요소들을 넣었을 때 오류가 발생하지 않는 경우

/* Set 을 선언할 때 요소의 타입을 정해 주지 않은 경우에는
그 Set 안에 있는 요소들의 타입이 서로 달라도 됨 */

var fruits = {'사과', '파인애플', '오렌지', 8};
print(fruits.runtimeType); // _HashSet<Object>

//String과 int의 상위 클래스인 Object로 추론

 

2) Set은 집합과 같아, 요소들의 순서가 보장되지 않음 *그래서 List와 다르게 Index라는 개념이 없음

var fruits = {'사과', '파인애플', '오렌지'};
print(fruits[2]); // Error: The operator '[]' isn't defined for the class 'Set<String>'.

//Set에 Index를 적용하면 오류남. []

 

3) 중복된 값이 있으면 하나를 제외하고는 모두 무시 *오류는 나지 않고 조용히 중복 값을 제거함

    ㄴ 그래서 Set은 주로 중복된 값을 제거하고 싶을 때 사용함

var fruits = {'사과', '파인애플', '사과'};
print(fruits); // {사과, 파인애플}

//중복된 값이 있으나 오류 나지 않고, 중복값을 제거 후 출력

 

4) length를 통해 요소의 개수를 구할 수 있음

final fruits = {'사과', '파인애플', '오렌지'};
print(fruits.length); // 3

 

5-1) isEmpty를 통해 Set에 요소가 없는지(Set이 비어있는지) 알 수 있음

//요소가 없으면 true, 요소가 하나라도 있으면 false를 반환
var numbers = <int>{};
print(numbers.isEmpty); // true
print(numbers.length); // 0

var fruits = {'사과', '파인애플', '오렌지'};
print(fruits.isEmpty); // false

 

5-2) isNotEmpty: Set에 요소가 있는지 판별

//요소가 하나라도 있으면 true, 요소가 없으면 false 반환
var numbers = <int>{};
print(numbers.isNotEmpty); // false

var fruits = {'사과', '파인애플', '오렌지'};
print(fruits.isNotEmpty); // true

 

6) add(), addAll()을 통해 요소를 추가할 수 있음

//add()는 요소 하나만 추가할 때, addAll()은 여러 요소를 추가할 때 사용

var fruits = {'사과', '파인애플', '오렌지'};
fruits.add('복숭아');
print(fruits); // {사과, 파인애플, 오렌지, 복숭아}

fruits.addAll({'포도', '귤'});
print(fruits); // {사과, 파인애플, 오렌지, 복숭아, 포도, 귤}

final additionalFruits = {'샤인머스켓', '수박'};
fruits.addAll(additionalFruits);
print(fruits); // {사과, 파인애플, 오렌지, 복숭아, 포도, 귤, 샤인머스켓, 수박}

 

7) remove()를 통해 요소를 삭제할 수 있음 / *Index 개념이 없으니까 removeAt()도 없음!

//삭제할 요소를 ()안에 넣으면 됨
var fruits = {'사과', '파인애플', '오렌지'};
fruits.remove('파인애플');
print(fruits); // {사과, 오렌지}

 

8) contains(), containsAll()을 통해 특정 요소가 있는지 알 수 있음

/* contains()는 하나의 요소가 포함되어 있는지 판별할 때
containsAll()은 여러 요소가 포함되어 있는지 판별할 때 사용, ()에 넣은 요소 중 하나라도 없으면 거짓으로 판별
var fruits = {'사과', '파인애플', '오렌지'};
print(fruits.contains('사과')); // true
print(fruits.contains('포도')); // false

print(fruits.containsAll({'사과', '오렌지'})); // true
print(fruits.containsAll({'사과', '귤'})); // false

3. Map

- 키(Key) 와 값(Value)이 묶인 하나의 쌍으로 이루어진 형태

  • Map<Key 타입, value 타입> 변수이름 = {key: value};
//각각의 요소는 ,로 구분
Map<String, String> people = {'Alice': 'Teacher', 'Bob': 'Student'};
// 요소 없는 Map도 만들 수 있음
Map<String, String> people = {};

 

  • 변수이름 = {key: value};
/* Map 요소에 변동이 있는 경우에는 var로 선언
이때 people은 Map<String, String> 타입으로 추론 */
var people = {'Alice': 'Teacher', 'Bob': 'Student'};
/* Map 요소에 변동이 없는 경우에는 final이나 const로 선언 가능
people을 Map<String, String> 타입으로 추론
animals를 Map<String, int> 타입으로 추론 */
final people = {'Alice': 'Teacher', 'Bob': 'Student'};
const animals = {'Dog': 3, 'Cat': 5};
// 요소 없는 Map 만들 수 있음
var people = {};

 

Map 특징

1) 키들과 값들은 각각 타입이 같아야 함

Map<String, int> people = {'Alice': 25, 'Bob': 'Teacher'};
// Error: A value of type 'String' can't be assigned to a variable of type 'int'.

 

* 참고) 타입이 달라도 오류가 발생하지 않는 경우

/* Map 을 선언할 때 요소의 타입을 정해 주지 않은 경우에는
그 Map 안에 있는 키들의 타입과 값들의 타입이 각각 통일되지 않아도 됨 */

var people = {'Alice': 25, 45: 'Bob'};
print(people.runtimeType); // LinkedMap<Object, Object>

//String과 int의 상위 클래스인 Object로 추론

 

2) 키와 값을 서로 다른 타입으로 구성할 수 있음

Map<String, int> people = {'Alice': 25, 'Bob': 30};
// 키는 String 타입, 값은 int 타입

 

3) 키는 중복될 수 없지만(Map의 키는 고유해야 함), 값은 중복될 수 있음

Map<String, int> people = {'Alice': 25, 'Bob': 25, 'Alice': 23};
print(people); // {Alice: 23, Bob: 25}

/* ‘Alice’ 키가 중복돼서 ‘Alice’ 키가 하나만 들어감(마지막에 정의된 값이 키에 저장)
25 값은 중복 가능 */

 

4) length를 통해 요소의 개수를 구할 수 있음

// 키와 값의 쌍이 하나라서 요소의 개수는 2개
Map<String, int> people = {'Alice': 25, 'Bob': 30};
print(people.length); // 2

 

5-1) isEmpty를 통해 Map에 요소가 없는지(Map이 비어있는지) 알 수 있음

// 요소가 없으면 true, 요소가 하나라도 있으면 false 반환
var names = {};
print(names.isEmpty); // true
print(names.length); // 0

var people = {'Alice': 25, 'Bob': 30};
print(people.isEmpty); // false

 

5-2) isNotEmpty: Map에 요소가 있는지 판별

// 요소가 하나라도 있으면 true, 없으면 false 반환
var names = {};
print(names.isNotEmpty); // false

var people = {'Alice': 25, 'Bob': 30};
print(people.isNotEmpty); // true

 

6) 키가 고유한 특성을 가지고 있기 때문에, [변수이름][[키이름]] 을 통해 값을 검색할 수 있음

Map<String, int> people = {'Alice': 25, 'Bob': 30};
print(people['Alice']); // 25

// List의 Index와 비슷한 형태로 생김

 

Q. Map에 없는 키를 넣으면? null을 반환함

Map<String, int> people = {'Alice': 25, 'Bob': 30};
print(people['Paul']); // null

 

7) [변수이름][[키이름]] = [값]; 을 통해 키의 값 수정 가능

Map<String, int> people = {'Alice': 25, 'Bob': 30};
people['Alice'] = 28;
print(people); // {Alice: 28, Bob: 30}
print(people['Alice']); // 28

 

8) [변수이름][[키이름]] = [값]; 을 통해 새로운 키와 값의 쌍을 추가할 수 있음

Map<String, int> people = {'Alice': 25, 'Bob': 30};
people['Charlie'] = 35;
print(people); // {Alice: 25, Bob: 30, Charlie: 35}

 

9) remove()를 통해 요소를 삭제할 수 있음 (Map은 Index 개념이 없어 removeAt()도 없음)

Map<String, int> people = {'Alice': 25, 'Bob': 30};
people.remove('Bob');
print(people); // {Alice: 25}
print(people['Bob']); // null

 

QnA. Map은 remove(key) 입력으로 전체(키: 값)쌍 을 삭제

QnA. Map은 key는 고유하지만, value 값은 중복될 수 있기 때문에 항상 key 로만 삭제해야함 * 값만으로는 삭제 불가

QnA. 키와 값 모두 지정해서 지우는 것도 가능 > removeWhere() 사용

//removeWhere 사용

Map<String, int> people = {'Alice': 25, 'Bob': 30, 'Charlie': 30};

// 'Bob'이고 값이 30인 경우만 삭제
people.removeWhere((key, value) => key == 'Bob' && value == 30); // Alice: 25, Charlie: 30

// 특정 값만 지우고 싶을때: 값이 30인 항목 모두 삭제
people.removeWhere((key, value) => value == 30); // Alice: 25
// 중복 값이 있을 수 있어 값만 가지고 특정 항목을 삭제할 수 없지만, 특정 값을 가진 모든 항목을 지울 때는 removeWhere() 메서드 사용으로 가능

 

10) containsKey()를 통해 특정 요소가 있는지 알 수 있음

Map<String, int> people = {'Alice': 25, 'Bob': 30};
print(people.containsKey('Alice')); // true
print(people.containsKey('Paul')); // false

 

11) keys를 통해 모든 키들을 알 수 있음

Map<String, int> people = {'Alice': 25, 'Bob': 30};
print(people.keys); // (Alice, Bob) 모든 키 출력

 

12) values를 통해 모든 값들을 알 수 있음

var students = {'Alice': 25, 'Bob': 30};
print(students.values); // (25, 30)

var teachers = {'Paul': 35, 'Henry': 37, 'Bella': 35};
print(teachers.values); // (35, 37, 35) 값은 중복될 수 있기 때문에 중복된 값도 모두 출력됨

😎 List / Set / Map 비교 (* 한번 더 확인/정리 필요)

항목 List Set Map
순서 유무 O X (집합 개념)
Index 유무 O (List[0]) X X
중복 허용 여부 O X 키는 중복 불가, 값은 허용
중복 제거 X (직접 제거해야함) O (자동 제거) 키 중복 시 마지막 값으로 덮어씀
키-값 구조 X X O ( key: value)
특정 항목 삭제 방법 remove(value) remove(value) remove(key) or removeWhere()