Dart

[Dart] 컬렉션 (Collections) - List, Set, Map

Meezzi 2025. 3. 11. 20:35
728x90

1. 컬렉션 (Collcetion)

컬렉션은 여러 개의 데이터를 한 번에 관리할 수 있는 자료 구조로

컬렉션을 사용하면 여러 데이터를 효율적으로 저장하고 관리할 수 있습니다.

 

Dart에는 대표적을 List, Set, Map이 있습니다.

 

 

2. 리스트(List) - 순서를 가진 친구들 👥

 

리스트는 순서가 있는 데이터의 집합으로 배열이라고도 불립니다.

순서가 있기에 인덱스를 통해 요소에 접근할 수 있어요.

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

List<String> fruits = ['사과', '바나나', '딸기'];
print(fruits[0]); // 사과

 

 

변수를 선언할 때는 var로도 선언할 수 있는데요!

물론 리스트도 var로 선언할 수 있습니다.

 

그리고 final과 const로도 변수를 선언한다는 것 기억하시나요?

만약, 각 요소의 변동이 없다면 final과 const로도 선언 가능하답니다!

var numbers = [1, 2, 3, 4];
var fruits = ['사과', '바나나', '딸기'];

final numbers = [1, 2, 3, 4];
const fruits = ['사과', '바나나', '딸기'];

 


만약 리스트 안에 요소를 넣고 싶지 않다면 이렇게도 선언할 수 있습니다.

List<int> numbers = [];
var numbers = [];
var numbers = <String>[];

 

 

1) 특징

 

이런 리스트는 어떤 특징이 있을까요?

 

첫번째로, 각각의 요소는 모두 같은 타입이어야 해요.

만약, numbers 리스트를 int 타입으로 선언했는데 갑자기 문자열로 저장하면 오류가 나요.

List<String> fruits = ['사과', '바나나', 5'];

/**
Error: A value of type 'int' can't be assigned to a variable of type 'String'.
List<String> fruits = ['사과', '바나나', 5']
**/

 

 

사실 다른 타입의 요소들을 넣었을 때 오류가 발생하지 않는 경우도 있긴 합니다..!

var fruits = ['사과', '바나나', 8];
print(fruits.runtimeType);
print(fruits);

// List<Object>
// [사과, 바나나, 8]

 

var는 초기값을 기반으로 타입을 추론한다는 것 기억하시나요?

다른 타입의 요소를 넣은 후, 리스트의 타입을 보면 List<Object> 가 출력되는 것을 확인하실 수 있습니다.

 

여기서 Object는 int, double, String 등의 상위 타입인데요. 

즉, int, double, String은 Object를 상속받은 것입니다.

때문에 다른 타입의 요소들도 넣을 수 있는 것이죠.

 

 

두 번째로 index를 통해 각 요소에 접근할 수 있어요.

 

리스트의 부제목 기억나시나요? 

바로 순서를 가진 친구들인데요. 

리스트는 순서를 가지고 있기에 각각의 요소에 index로 접근가능하답니다!

List<int> numbers = [10, 20, 30];
print(numbers[0]); // 10
print(numbers[1]); // 20

 

다만 주의할 점은 리스트의 첫 번째 요소는 0부터 시작해요.

첫 번째 요소라고 1부터 시작한다고 생각하시면 안 됩니다!

 

참고로 index에 음수를 넣으면 RangeError가 발생해요.

 

세 번째는 index를 통해 요소를 바꿀 수도 있습니다

하지만 const와 final로 선언한 리스트는 바꾸지 못한다는 점!

List<int> numbers = [10, 20, 30];
numbers[1] = 99; 
print(numbers); 	// [10, 99, 30]

 

 

마지막으로는 중복된 값을 허용해요!

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

 

 

2) 관련 메서드

 

메서드 설명
add 하나의 요소 추가
addAll 여러 요소 추가
remove 요소 삭제
removeAt 특정 인덱스의 요소 삭제
clear 모든 요소 삭제
length 요소의 개수
isEmpty 요소가 없으면 true, 하나라도 있으면 false
isNotEmpty 요소가 하나라도 있으면 true, 없으면 false
indexOf 특정 요소의 index
contains 값이 있는지 확인

 

 

var fruits = ['사과', '바나나', '딸기'];
fruits.add('배');
print(fruits);
  
fruits.addAll(['체리', '수박', '포도']);
print('addAll 결과 : $fruits');
  
fruits.remove('사과');
print('remove 결과 : $fruits');
  
fruits.removeAt(3);
print('removeAt 결과 : $fruits');
  
print('length : ${fruits.length}');
  
print('isEmpty : ${fruits.isEmpty}');
  
print('isNotEmpty : ${fruits.isNotEmpty}');
  
print('indexOf : ${fruits.indexOf('파인애플')}');
  
print('contains : ${fruits.contains('배')}');
  
fruits.clear();
print('fruits : $fruits');


/**
[사과, 바나나, 딸기, 배]
addAll 결과 : [사과, 바나나, 딸기, 배, 체리, 수박, 포도]
remove 결과 : [바나나, 딸기, 배, 체리, 수박, 포도]
removeAt 결과 : [바나나, 딸기, 배, 수박, 포도]
length : 5
isEmpty : false
isNotEmpty : true
indexOf : -1
contains : true
fruits : []
**/

 

 

3. Sets - 중복은 가라! 🚫

앞서 List는 중복을 허용한다는 것 기억나시나요?

Set는 List와 달리 중복을 허용하지 않는 데이터 구조입니다.

 

Set<타입> [변수 이름] = {요소};

Set<int> numbers = {1, 2, 3, 4};
var numbers = {1, 2, 3, 4};

 

 

만약 요소를 넣고 싶지 않다면 이렇게도 할 수 있어요.

Set<int> numbers = {};
var names = <String>{};

 

 

 

1) 특징

중복이 허용되지 않는 Set에는 어떤 특징이 있을까요?

 

첫 번째로 각각의 요소는 모두 같은 타입이어야 해요.

이건 List와 똑같은 특징이죠?

 

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

Set<String> fruits = {'사과', '바나나', '딸기', 8};
// Error: A value of type 'int' can't be assigned to a variable of type 'String'

 

 

또한, List와 마찬가지로 타입이 다른 요소들을 넣었을 때 오류가 발생하지 않는 경우도 있습니다.

var fruits = {'사과', '바나나', '딸기', 8};
print(fruits.runtimeType);		// LinkedSet<Object>

 

 

두 번째로 요소들의 순서가 없다는 점인데요.

순서가 없기 때문에 List처럼 [0], [1] 의 방식으로 요소에 접근할 수 없습니다.

 

그렇다면 Set에서는 어떻게 요소에 접근할까요?

 

바로 for문을 이용하는 것입니다.

Set<String> fruits = {'사과', '바나나', '딸기'};

// for-in
for (var fruit in fruits) {
  print(fruit);
}

print('-------------');

// forEach()
fruits.forEach((fruit) => print(fruit));

/**
사과
바나나
딸기
-------------
사과
바나나
딸기
**/

 

 

세 번째는 중복된 값을 저장할 수 없다는 것입니다. 

Set의 가장 큰 특징이에요.

같은 값이 여러 번 추가되면 첫 번째로 저장된 값만 남고 나머지는 무시됩니다.

Set<String> fruits = {'사과', '바나나', '딸기', '사과', '바나나'};

print(fruits);		// {사과, 바나나, 딸기}

 

 

2) 관련 메서드

메서드 설명
add 하나의 요소 추가
addAll 여러 요소 추가
remove 요소 삭제
clear 모든 요소 삭제
length 요소의 개수
isEmpty 요소가 없으면 true, 하나라도 있으면 false
isNotEmpty 요소가 하나라도 있으면 true, 없으면 false
contains 값이 있는지 확인

 

 

var fruits = {'사과', '바나나', '딸기'};
fruits.add('배');
print(fruits);
  
fruits.addAll(['체리', '수박', '포도']);
print('addAll 결과 : $fruits');
  
fruits.remove('사과');
print('remove 결과 : $fruits');
  
  
print('length : ${fruits.length}');
  
print('isEmpty : ${fruits.isEmpty}');
  
print('isNotEmpty : ${fruits.isNotEmpty}');
 
  
print('contains : ${fruits.contains('배')}');
  
fruits.clear();
print('fruits : $fruits');


/**
{사과, 바나나, 딸기, 배}
addAll 결과 : {사과, 바나나, 딸기, 배, 체리, 수박, 포도}
remove 결과 : {바나나, 딸기, 배, 체리, 수박, 포도}
length : 6
isEmpty : false
isNotEmpty : true
contains : true
fruits : {}
**/

 

 

4. Map - We are One! 🤝

Map은 키(Key)와 값(Value)이 하나의 쌍으로 데이터를 저장하는 구조를 가졌어요.

 

Map<Key 타입, value 타입> 변수 이름 = {key: value};

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

print(ages);		// {Alice: 25, Bob: 30}

 

 

Map에 어떤 요소도 넣고 싶지 않다면 이렇게 작성해요!

Map<String, int> = people = {};
var people = {};

 

 

1) 특징

 

Map에는 어떤 특징이 있을까요?

 

첫 번째는 Key와 Value는 각각 타입이 같아야 해요.

Map<String, int> ages = {'Alice' : 25, 'Bob' : 'student'};

print(ages);

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

 

 

마찬가지로 타입이 달라도 오류가 발생하지 않는 경우도 있답니다.

var ages = {'Alice' : 25, 'Bob' : 'student'};
print(ages.runtimeType);
print(ages);

/**
IdentityMap<String, Object>
{Alice: 25, Bob: student}
**/

 

 

두 번째는 Key를 통해 Value를 저장하고 찾을 수 있어요.

List는 인덱스를 통해 요소를 찾지만, Map은 Key를 이용하는 것이죠!

var ages = {'Alice' : 25, 'Bob' : 30};

print(ages['Alice']);		// 25

 

 

세 번째로 Map의 Key는 중복될 수 없지만, Value는 중복될 수 있어요.

만약, 같은 Key가 두 번 들어가면 마지막 값이 덮어쓰기 됩니다.

var ages = {'Alice' : 25, 'Bob' : '30', 'Alice' : 35};

print(ages['Alice']);		// 35

 

 

네 번째로 빠르게 검색이 가능하다는 점인데요.

Map은 List보다 특정 값을 찾는 속도가 더 빠릅니다.

 

왜 그러냐면, List에서 값을 찾으려면 모든 요소를 순차적으로 검사하기 때문에 시간이 오래 걸려요.

반면, Map은 Hash Table 구조를 갖고 있기 때문에 Key를 이용해서 빠르게 찾을 수 있어요.

 

여기서 Hash Table은 특정한 값을 빠르게 찾기 위한 자료구조예요.

흔히 아는 사전이나 전화번호부와 비슷한 개념이에요.

 

비유를 하자면 전화번호부에서 이름으로 전화번호를 찾을 때, 첫 번째 페이지부터 하나하나 찾으면 시간이 오래 걸리지만,

Hash Table을 사용하면 이름 -> 특정 페이지로 바로 이동해서 한 번에 찾을 수 있어요.

 

Map에서는 Key를 Hash Funtion(해시 함수)을 이용해 숫자로 변환해요.

이 Hash Function은 어떤 입력값(Key)이 들어오더라도 항상 같은 숫자(Hash Value)를 반환하는 함수예요.

 

Key Hash Function(해시 함수) 변환 -> 해시 값(저장 위치)
'Alice' 101번 위치
'Bob' 205번 위치

 

즉, ages['Alice']를 검색할 때 Alice를 Hash Funtion에 넣어 해시 값을 구해요.

이 위치로 바로 이동하여 데이터를 반환(25)하는 거죠.

 

 

2) 관련 메서드

메서드 설명
[변수 이름][키 이름]] = [값] 새로운 키와 값 추가
remove 요소 삭제
clear 모든 요소 삭제
length 요소의 개수
isEmpty 요소가 없으면 true, 하나라도 있으면 false
isNotEmpty 요소가 하나라도 있으면 true, 없으면 false
[변수 이름][[키 이름]] 검색
[변수 이름][[키 이름]] = [값] 키의 값 수정
containsKey() 특정 요소가 있으면 true, 없으면 false 반환
keys 모든 키 반환
values 모든 값 반환

 

Map<String, int> ages = {'Alice' : 25, 'Bob' : 30};
print(ages);

ages['Nana'] = 20;
print(ages);
  
ages.remove('Alice');
print('remove 결과 : $ages');
  
print('length : ${ages.length}');
  
print('isEmpty : ${ages.isEmpty}');
  
print('isNotEmpty : ${ages.isNotEmpty}');
  
print(ages['Bob']);
  
ages['Bob'] = 15;
print(ages);
 
print('containsKey : ${ages.containsKey('Anna')}');
  
print(ages.keys);
  
print(ages.values);
 
ages.clear();
print('ages : $ages');

/**
{Alice: 25, Bob: 30}
{Alice: 25, Bob: 30, Nana: 20}
remove 결과 : {Bob: 30, Nana: 20}
length : 2
isEmpty : false
isNotEmpty : true
30
{Bob: 15, Nana: 20}
containsKey : false
(Bob, Nana)
(15, 20)
ages : {}
**/

 

 

 

5. 참고자료

https://dart.dev/language/collections

 

Collections

Summary of the different types of collections in Dart.

dart.dev

 

728x90