Dart는 아래와 같은 자료형을 지원한다:
int
, double
)String
)bool
)List
)Sets
)Map
)Runes
)Symbol
)null
(Null
)그 외에도 다른 몇 가지의 비교적 한정적인 역할을 하는 자료형들이 있다:
Object
: Null
을 제외한 모든 Dart 클래스들의 상위 클래스Enum
: 모든 열거형들의 상위 클래스Future
, Stream
: 비동기 처리에 활용됨Iterable
: for-in 반복문 또는 동기적인 Generator
함수에 사용됨 (Generator
에 대해서는 나중에 알아보도록 하자)Never
: Never
가 포함된 Expression이 무슨 값을 갖는지 평가할 수 없음을 나타냄, 보통은 항상 예외를 던지는 함수에 사용된다dynamic
: 이 자료형을 사용한 변수에 대해 컴파일 전에 자료형 확인을 하지 않겠다는 것을 나타냄, Object
나 Object?
를 대신 사용 가능void
: 값이 절대 사용될 일이 없음을 나타냄추가적으로 Object
, Object?
, Never
그리고 Null
은 Dart의 전체 클래스 계층 구조에서 특별한 의미를 갖고 있다.
간단히 언급하자면, Null Safety 적용 전에는 클래스 계층 구조가 상단부터 Object
- 각종 자료형 등 Dart 클래스 - Null
이었는데, 적용 이후에는 Null
이 별도로 분리되면서 Object
의 최하단에는 Never
가, Object?
의 최하단에는 Null
이 위치하게 되었다는 이야기다. 다시 말해 non-nullable 객체에서 Null
을 대체하게 된 것이 Never
라는 말.
해당 내용에 대해서는 Understanding null safety | Dart의 top-and-bottom 부분을 참고하도록 하자.
Dart의 숫자 자료형은 아래와 같이 두 가지 형태를 갖는다:
int
(정수)정수는 네이티브 플랫폼에서는 -2^63에서 2^63 - 1의 범위를 갖고, 웹 플랫폼에서는 -2^53에서 2^53 - 1의 범위를 갖는다.
이렇게 된 이유는 네이티브에 플랫폼에서는 64 비트 부호 있는 2의 보수법으로 정수를 표현하지만, 웹으로 빌드 시에는 64 비트 부동 소수점 형식으로 변환되기 때문.
일단 Dart에서는 숫자 구현 방식의 차이점으로 인한 문제는 많이 발생하지 않으며, 구현 방식이 사용자에게 숨겨지도록 설계되었기 때문에 int
와 double
자료형을 여러분이 알던 그대로 평범하게 대하라고 조언해주고 있다.
어쨌든 이 내용에 대해 더 궁금한 점이 있는 분께서는 Numbers in Dart | Dart를 참고하기 바란다.
double
(실수)64 비트 부동 소수점으로 구현되었다.
int
와 double
클래스는 둘 다 num
클래스의 하위 클래스이다.num
클래스는 사칙연산과 같은 기본적인 연사자를 제공하며 abs()
, ceil()
그리고 floor()
과 같은 다른 함수들도 제공한다. >>
와 같은 비트 연산자는 int
클래스에 정의되어 있으며, 다른 추가적인 숫자를 다루는 함수들을 살펴보고 싶다면 dart:math 라이브러리를 참고하기 바란다.
int
의 비트 연산자int
자료형은 <<, >>, >>>, ~(보수), &(AND), |(OR), ^(XOR) 연산자를 지원한다.
int
의 16 진법 표기int
자료형은 16 진법 표기를 지원한다. int a = 0xABCD1234
와 같은 식으로 선언 가능.
String
과 숫자 간 변환int
와 double
자료형은 클래스 수준 함수로 parse(args)
를 지원한다. args
에 String
을 입력하면 그에 맞는 숫자로 변환해주는 식이다. 아래 코드르 참고하기 바란다:
int intValue = int.parse("1004");
double doubleValue = double.parse("3.141592");
반대로 int
와 double
을 String
으로 바꾸는 것도 가능. 숫자 뒤에 toString()
함수를 붙여주기만 하면 된다. 다만 double
의 경우 String
으로 변환하는 함수가 4개 존재하고, 상황에 따라 적절하게 사용하면 될 듯 하다.
String intStr = 1004.toString();
String doubleStr = 3.141592.toString();
double.toString()
: 기본적인 변환double.toStringAsExponential([int? fractionDigits])
: 소수점 아래 fractionDigit
자리까지 3.14e+02와 같은 지수 표현식으로 변환double.toStringAsFixed(int fractionDigits)
: 소수점 아래 fractionDigits
자리까지 변환 double.toStringAsPrecision(int precision)
: Bit precision에 따른 변환String
Dart의 문자열은 UTF-16 코드 하나하나의 나열로 구성되어 있다. 선언에는 '(작은 따옴표)와 "(큰 따옴표)를 둘 다 활용할 수 있다.
String big = "큰 따옴표";
String small = '작은 따옴표';
double pi = 3.14;
String str = "원주율은 $pi입니다.";
${expression}
을 통해 문자열 안에 변수를 표기할 수 있다. 만약 expression이 식별자라면 중괄호를 생략해도 된다.
문자열 간 + 연산자를 통해 두 문자열을 접합할 수 있다.
작은 따옴표 또는 큰 따옴표를 3개 연달아 사용해서 여러 줄의 문자열을 나타낼 수 있다.
String str = '''
이것은
굉장히
길고
긴
문자열입니다.
''';
이게 무슨 말이냐 하면, 아래 코드와 같은 상황을 의미한다:
String badStr = "누군가 말했다. "도망쳐!""; // 오류 발생함
이상의 코드는 오류를 일으킨다. 따라서 문자열 안에 작은 따옴표 또는 큰 따옴표를 넣을 생각이라면, 1) 문자열 안에 넣을 따옴표와 문자열을 감쌀 따옴표를 분리해서 사용하기 바란다. 예를 들어 큰 따옴표를 문자열에 넣을 생각이라면, 문자열 자체는 작은 따옴표로 감싸야 한다. 아래 코드처럼 말이다:
String goodStr = '누군가 말했다. "도망쳐!"';
bool
bool
자료형에 할당될 수 있는 값은 컴파일 상수에 해당하는 true
와 false
말고는 없다.
bool
자료형의 활용보통 Python에서 if 문을 사용할 때, 이렇게 쓰기도 한다:
boolValue = 1
if boolValue:
# TODO
하지만 Dart에서는 bool
자료형이 아닐 경우 if 문 안에 직접 집어넣을 수 없다. 대안으로 값을 비교하거나 bool
값을 반환하는 함수를 사용하도록 하자.
// 잘못된 예시
int intValue = 1;
if (intValue) {
// TODO
}
// 더 좋은 예시
// 1. bool 자료형을 반환하는 함수 사용
String strValue = "";
if (strValue.isEmpty()) {
// TODO
}
// 2. 값 비교
int intValue = 1;
if (intValue == 1) {
// TODO
}
List
List
는 모두가 아는 배열에 해당하는 자료형이다. 아래와 같은 방법으로 선언할 수 있다:
// 한 줄로 선언
var list1 = [1, 2, 3];
// 여러 줄로 선언
var list2 = [
'Samsung',
'LG',
'Apple'
];
List
자료형과 Collection에 대해Dart에서는 여러 값을 동시에 다룰 수 있는 유용한 도구들을 제공한다. 거기에는 List
, Set
등 기본적인 자료형도 존재하지만, Collection 라이브러리를 통해 큐, 연결 리스트 등 풍부한 도구를 추가적으로 제공하고 있다.
Collection 라이브러리에 대한 내용은 링크를 참고하기 바란다.
List
는 C 언어에서 보던, 0부터 시작하는 인덱싱을 지원한다.
또한 List.length
를 통해 List
의 길이를 확인할 수 있다.
첫 글에서도 언급한 바 있지만, List
는 Spread 연산자(...)와 Null-aware spread 연산자(...?)를 지원한다. 이 연산자는 Collection에 여러 값을 동시에 삽입할 수 있게 해 준다. 아래 코드를 참고하자:
var list1 = [1, 2, 3];
var list2 = [0, ...list1];
print(list2.length); // 리스트 길이는 4로 출력됨
Spread 연산자(...)를 적용할 리스트가 null
값을 가질 가능성이 있는 경우에는 Null-aware spread 연산자(...?)를 사용하도록 하자.
이 역시 첫 글에서도 잠깐 언급한 내용이다. Collection if는와 for은 리스트를 선언할 때 반복문과 조건문을 활용할 수 있게 해 준다.
아래 코드를 참고하자:
// Collection if
var list1 = [1, 2, 3, if (condition) 4]; // condition 만족 시 list1 = [1, 2, 3, 4]
// Collection for
var list2 = [1, 2, 3];
var list3 = ["0번", for (var i in list2) "$i번"]; // ["0번", "1번", "2번", "3번"]
Set
집합을 나타내는 자료형이다. {}(중괄호)를 통해 선언할 수 있다. 아래 코드처럼:
var set1 = {1, 2, 3};
다만 위 코드에서 중요한 점은, Dart는 set1
을 Set<int>
으로 추측한다는 사실이다. 따라서 만약 사용자가 set1
에 정수가 아닌 다른 자료형을 넣으려고 시도할 경우, Dart는 오류를 반환하게 된다. 따라서 필요한 경우 자료형을 확실히 명시하여 set
을 선언하는 것도 고려해볼 수 있겠다.
var set2 = <String>{};
Set<String> set3 = {};
var set = {1, 2, 3};
set.add(4);
Set.add(args)
또는 Set.addAll(args)
함수를 사용하자. 전자의 경우는 단일 원소, 후자의 경우는 여러 원소를 집합에 추가할 때 사용한다.
var set = {1, 2, 3};
print(set.length);
Set.length
를 통해 집합 길이를 확인할 수 있다.
Map
일반적으로 우리가 알고 있는 해시 맵은 Python의 Dictionary와 비슷한, 여러 개의 키와 값을 각각 연결한 것들이다. 이 키와 값은 어떤 자료형이든 다 넣을 수 있다.
한편, 각각의 키는 고유해야 하지만 값은 여러 키에 대해 중복하여 사용할 수 있다.
Map
의 경우는 아래와 같이 선언할 수 있지만, List
와 비슷하게 var
로 선언할 경우 Dart가 스스로 자료형을 추측하게 된다. 따라서 웬만해서는 자료형을 명시하여 선언해줄 수 있도록 하자:
// Dart는 map1을 Map<String, String>으로 추측함
var map1 = {
"철수": "반장",
"영희": "부반장",
"이즈리얼": "총무"
};
// 자료형을 명시하여 Map 선언하기
var map2 = <int, String>{};
Map<int, String> map3 = {
1: "감자",
2: "당근",
3: "칡"
};
Python에서 Dictionary에 추가하던 그 방식을 그대로 사용하면 된다:
var map = <int, String>{};
map[0] = "수박";
Map.length
를 통해 집합 길이를 확인할 수 있다.
Map<int, String> map = {
1: "감자",
2: "당근",
3: "칡"
};
print(map.length);
해시 맵도 Collection의 일종이라 Spread 연산자를 사용할 수 있다. 아래 코드를 참고하자:
var map1 = <int, String>{1: "이즈리얼", 2: "베인", 3: "자야"};
var map2 = <int, String>{...map1, 4: "진", 5: "제리", 6: "시비르"};
print(map2); // {1: 이즈리얼, 2: 베인, 3: 자야, 4: 진, 5: 제리, 6: 시비르}
리스트를 해시 맵으로 바꿀 수도 있다. 먼저 리스트 하나에 번호를 매기고 싶을 경우:
var list1 = ["이즈리얼", "카직스", "신드라"];
var map3 = list1.asMap();
print(map3); // {0: 이즈리얼, 1: 카직스, 2: 신드라}
다음으로는 두 개의 리스트를 하나의 맵으로 합치고 싶을 경우:
var list1 = ["이즈리얼", "카직스", "신드라"];
var list2 = ["원거리 딜러", "정글러", "미드 라이너"];
var map4 = Map.fromIterables(list1, list2);
print(map4); // {이즈리얼: 원거리 딜러, 카직스: 정글러, 신드라: 미드 라이너}
그 외에 Map.fromEntries()
, Map.fromIterable()
도 존재하므로, 필요한 경우 Map 클래스 문서를 참고하기 바란다.
Runes
룬이란 문자열을 구성하는 각 문자의 유니코드 값을 나타내는 자료형이다.
룬에 대한 정확한 이해를 위해서는 유니코드에 대해 조금 알 필요가 있다. 유니코드 문자들에는 1 문자에 1 코드 포인트(Code point)가 할당되어 있다. 또한 인코딩 바이트 수마다 다르게 표현된다.
따라서 동일한 코드 포인트를 갖고 동일한 인코딩 방식을 사용하더라도, 인코딩 바이트 폼에 따라 표현되는 형식이 다를 수 있다. 예를 들어 알파벳 M의 경우, 코드 포인트가 U+004D이지만 UTF-8에서는 4D, UTF-16에서는 004D이다.
어쨌든 Dart의 문자열은 UTF-16 코드 유닛들의 나열로 이루어져 있기 때문에, 코드 포인트를 문자열로 표현하기 위해서는 특별한 문법이 필요하다. 이를 표현하는 가장 보편적인 방식은 \uXXXX
의 4자리 16진수 값으로 작성하는 것. 예를 들어 '♥'의 코드 포인트는 \u2665
이다.
4자리보다 길거나 짧은 코드 포인트를 다루기 위해서는, 값을 {}(중괄호) 안에 넣어서 활용해보자. \u{1f606}
처럼.
Symbol
심볼이란 Dart 프로그램 내에서 선언된 식별자나 연산자를 표현하는 데 쓰이는 자료형이다. 아마 웬만해서는 사용자가 이걸 쓸 일이 없을 테니 여기서는 넘어가고, 필요한 분들은 Symbols | Language tour | Dart를 참고하길 바란다.
var
과 dynamic
var
과 dynamic
자료형은 어떤 자료형도 할당받을 수 있는, 상당히 유동적인 자료형이다. 다만 이 두 자료형에는 약간의 차이가 있다.
먼저 공통점으로는 두 자료형 모두 변수에 할당된 값을 바꿀 수 있다. 다만 dynamic
의 경우는 추가로, 원래 자료형과 다른 자료형의 값으로도 재할당이 가능하다. var
은 불가능.
var
자료형은 선언 및 초기화 시 할당되는 값에 의해 자료형이 결정되며, 일단 한 번 정해진 자료형은 추후 변경할 수 없다. 초기화할 때 int
값을 할당했다면, 그 변수는 그 순간부터 int
자료형으로 취급받는 것.
아래 코드를 참고해보자:
var a = 10; // 이 순간부터 변수 a는 int 값만 받을 수 있음
print(a.runtimeType.toString()) // int 출력됨
a = 30; // 문제 없이 재할당 됨
a = "Hello, world!" // 여기서 오류 발생
이 코드를 돌려보면 기존 10이었던 값을 30으로 바꿀 수는 있지만, 문자열로는 바꿀 수 없다는 걸 알 수 있다.
반면 dynamic
자료형은 자료형에 관계없이 어떤 값으로든 재할당이 가능하다. 초기화할 때 int
로 선언했어도 재할당은 String
이든 List
든 bool
이든 어떤 자료형이든 상관이 없다.
dynamic a = 10;
print(a.runtimeType.toString()) // int 출력됨
a = 30; // 문제 없이 재할당 됨
a = "Hello, world!"; // 문제 없이 재할당 됨
print(a.runtimeType.toString()) // String 출력됨