변수, 타입, Null Safety
이미 Dart를 써봤다면 기본 변수 선언은 익숙할 겁니다. 이 챕터는 빠른 복습을 목표로 핵심 내용만 압축합니다. Null Safety는 Dart 3.x에서 완전히 정착했으므로, 사운드 null safety 관점에서 정확히 이해하고 넘어갑니다.
변수 선언 방법
// var: 타입 추론, 재할당 가능
var name = 'Dart';
name = 'Flutter';
// final: 한 번 할당 후 불변 (런타임 값 가능)
final now = DateTime.now();
// const: 컴파일 타임 상수 (리터럴만 가능)
const pi = 3.14159;
const version = '3.0';
// 타입 명시 (optional)
String language = 'Dart';
int count = 0;
final과 const의 차이를 명확히 구분합니다.
// final: 런타임에 결정되는 값도 OK
final startTime = DateTime.now(); // 실행할 때마다 다른 값
// const: 반드시 컴파일 타임에 알 수 있어야 함
const maxRetry = 3; // 리터럴 상수만 가능
// const now = DateTime.now(); // 오류! 컴파일 타임에 알 수 없음
기본 내장 타입
// 숫자
int age = 30;
double height = 175.5;
num value = 42; // int와 double의 상위 타입
// 문자열
String greeting = 'Hello, Dart!';
String multiLine = '''
여러 줄
문자열
''';
String interpolated = 'My name is $name and I am ${age + 1} next year.';
// 불리언
bool isActive = true;
bool isEmpty = false;
// 동적 타입 (사용 자제)
dynamic anything = 42;
anything = 'now a string'; // 타입 오류 없음. 런타임에서 발견됨.
// Object? (최상위 타입)
Object? obj = 42;
obj = 'string';
Null Safety 핵심 개념
Dart 3.x에서는 모든 타입이 기본적으로 non-nullable입니다.
// non-nullable: null 불가
String name = 'Dart';
// name = null; // 컴파일 오류!
// nullable: ? 붙이면 null 가능
String? nickname = null;
int? age = null;
null 체크 방법
String? nickname = getUserNickname();
// 1. if로 체크
if (nickname != null) {
print(nickname.toUpperCase()); // 스마트 캐스트로 non-null 처리
}
// 2. null 병합 연산자 ??
final display = nickname ?? 'Anonymous';
// 3. null 조건 접근 ?.
final length = nickname?.length; // null이면 null 반환
// 4. null 조건 할당 ??=
nickname ??= 'Guest'; // null이면 'Guest' 할당
// 5. null 단언 ! (사용 주의)
final upper = nickname!.toUpperCase(); // null이면 런타임 예외 발생
late 키워드
선언 시점에는 초기화할 수 없지만, 사용 전에 반드시 초기화할 것을 보장하는 경우에 사용합니다.
class Database {
late Connection _connection; // 선언 시점에 초기화 불가
Future<void> connect(String url) async {
_connection = await Connection.open(url); // 나중에 초기화
}
Future<void> query(String sql) async {
// _connection이 초기화되지 않으면 런타임 에러
return _connection.execute(sql);
}
}
// late final: 한 번만 초기화 가능
class Config {
late final String apiKey;
Config(Map<String, String> env) {
apiKey = env['API_KEY'] ?? ''; // 생성자에서 초기화
}
}
흐름 분석 (Flow Analysis)
Dart 컴파일러는 코드 흐름을 분석해서 null 여부를 자동으로 판단합니다.
String? value = getValue();
// 흐름 분석: if 이후 non-null임을 인식
if (value == null) return;
print(value.length); // String으로 승격 — ? 없이 접근 가능
// 타입 검사 후 승격
Object obj = getObject();
if (obj is String) {
print(obj.toUpperCase()); // String으로 승격
}
// 흐름 분석의 한계: 외부에서 변경될 수 있는 경우
String? field;
void check() {
if (field == null) return;
// field가 getter라면 다른 스레드에서 변경될 수 있음
// final local = field; 로 먼저 잡아두는 것이 안전
final local = field!;
print(local.length);
}
타입 변환
// String → int/double
int n = int.parse('42');
double d = double.parse('3.14');
// 실패 시 null 반환 (예외 없음)
int? safe = int.tryParse('abc'); // null
// 숫자 → String
String s = 42.toString();
String formatted = 3.14159.toStringAsFixed(2); // '3.14'
// int ↔ double
int i = 5;
double dbl = i.toDouble();
int back = dbl.toInt(); // 소수점 버림
// bool
bool b = true;
String bs = b.toString(); // 'true'
정리
핵심 요점만 정리합니다.
var/final/const— 용도에 맞게 선택합니다. 기본은final로 시작합니다.- 모든 타입은 non-nullable. null을 허용하려면
?를 붙입니다. ??,?.,??=,!연산자로 null을 안전하게 처리합니다.late는 초기화 시점을 늦출 때 사용하되, 실제로 초기화되지 않으면 런타임 에러가 납니다.- 흐름 분석으로 컴파일러가 nullable 타입을 자동으로 승격시켜줍니다.