Dart 튜토리얼 36편: Dart Cheatsheet(핵심 문법 빠르게 복습)

요약#

핵심 요지#

  • 문제 정의: 문법이 손에 안 익으면, 같은 일을 더 길고 복잡하게 구현하게 된다.
  • 핵심 주장: 이 글은 “테이블로 빠르게 찾고, 접기/펼치기에서 코드로 확인”하는 치트시트 형태로 핵심 문법을 정리한다.
  • 주요 근거: 빠른 인덱스(테이블)에서 핵심 문법/패턴을 정리하였다.
  • 실무 기준: “지금 당장 쓰는 문법(문자열·null·컬렉션) → 함수 파라미터 → 예외/생성자” 순서로 복습한다.

참고 자료#


문제 상황#

치트시트는 “빠르게 찾는 것”이 목적입니다.
그래서 이 글은 긴 설명 대신, 자주 쓰는 문법을 표로 먼저 정리하고, 필요한 것만 펼쳐서 예제 코드로 확인하는 구조를 택합니다.


해결 방법#

빠른 인덱스(테이블)#

주제핵심 문법/패턴한 줄 설명펼쳐보기
변수/상수var, final, const, late값 바인딩(가변/불변/지연 초기화)보기
기본 타입bool, int, double, num, String숫자/불리언/문자열 핵심 타입보기
타입 시스템Object, dynamic, Never, void타입 상단/동적/불가능/반환 없음보기
주석//, /* */, ///코드 설명과 API 문서 주석보기
애노테이션@Deprecated, @override선언에 추가 정보를 붙임보기
import / exportimport, as, show/hide라이브러리 심볼 가져오기/제한보기
연산자 모음산술/비교/논리/비트/대입계산/조건/비트 작업을 표현보기
문자열 보간${expression} / $identifier문자열 안에 값을 삽입보기
문자열 리터럴raw(r''), multi-line('''/""")이스케이프 무시/여러 줄 문자열보기
nullable 변수T?null 가능 타입 선언보기
null-aware 연산자??, ??=null 대체/한 번만 대입보기
조건부 접근?.null이면 접근/호출을 생략보기
null assertionexpr!null이 아님을 단언(위험)보기
타입 테스트/캐스트is, is!, as런타임 타입 검사/변환보기
assertassert(condition)개발 중 조건 검증보기
분기if, switch, ?:, switch expression조건에 따라 실행/값 선택보기
반복for, for-in, while, do-while, break, continue반복 실행과 흐름 제어보기
라벨label:, break label, continue label중첩 반복을 한 번에 제어보기
컬렉션 리터럴[], {}, {'k': v}, <T>[]List/Set/Map을 빠르게 생성보기
컬렉션 if/forif (...) ..., for (...) ...리터럴 안에서 조건/반복 생성보기
스프레드..., ...?다른 컬렉션 원소를 펼쳐 추가보기
화살표 문법=>한 줄 함수(표현식 반환)보기
함수 파라미터optional/required/named파라미터 설계(기본값/필수)보기
익명 함수/클로저(x) { ... }, 캡처함수를 값처럼 전달/상태 캡처보기
제너레이터sync*, yield, yield*여러 값을 순차 생성보기
typedeftypedef F = ...복잡한 타입에 이름 붙이기보기
cascades.., ?..같은 객체에 작업을 연속 적용보기
getter / setterget, set필드 접근에 계산/검증 추가보기
옵션 위치 파라미터[]위치 인자를 선택적으로 받기보기
이름 있는 파라미터{} / required호출 시 이름으로 전달보기
Records(a, b), (x: 1, y: 2)여러 값을 한 덩어리로 전달보기
Generics<T>, List<T>, class Box<T>타입을 파라미터처럼 재사용보기
클래스 기본class, abstract, implements, with객체/상속/구현/조합보기
생성자 모음default/named/factory/redirecting/const초기화 방식 패턴 모음보기
정적 멤버static클래스에 귀속되는 멤버보기
프라이빗(가시성)_name라이브러리 단위 private보기
오버라이드@override, super상속 메서드 재정의/호출보기
operator / calloperator +, call()연산자/함수 호출처럼 동작보기
mixinmixin, mixin class기능을 조합해서 재사용보기
enumenum고정된 값 집합보기
extensionextension기존 타입에 메서드 추가보기
extension typeextension type새 타입처럼 보이게 감싸기보기
class modifiersbase, interface, final, sealed타입 재사용을 제한해 API 보호보기
patternsswitch/case/destructure형태 매칭으로 분기/추출보기
예외 처리try/on/catch/rethrow/finally오류를 잡고 정리/전파보기
async/awaitFuture, async, await비동기 코드를 순서대로 작성보기
StreamStream, async*, yield여러 이벤트를 시간에 따라 처리보기

Variables & constants(변수/상수)var, final, const, late

설명: 값이 변할 수 있으면 변수, 바뀌지 않게 하려면 상수를 사용합니다.
final1은 “한 번만 대입”, const2는 “컴파일 타임 상수”라는 차이가 있습니다.
late3는 “나중에 초기화”할 때 사용합니다.

var count = 0; // 타입 추론(type inference)[^type-inference]
count = 1;
final now = DateTime.now(); // 실행 시점에 값이 결정됨
// now = DateTime.now(); // 다시 대입 불가
const pi = 3.141592; // 컴파일 타임 상수
late final String token;
token = 'abc123'; // 늦게 초기화(한 번만 대입)

주의: const는 “실행해봐야 알 수 있는 값”(예: 현재 시간)을 담을 수 없습니다.

Built-in types(기본 타입)bool, int, double, num, String

설명: Dart의 기본 타입(built-in types)4에는 숫자(int, double, num), 불리언(bool), 문자열(String) 등이 포함됩니다.

bool ok = true;
int a = 1;
double b = 1.5;
num c = a + b; // num은 int/double을 모두 표현
String s = 'hello';

주의: intdoublenum의 하위 타입입니다.

Type system(타입 시스템)Object, dynamic, Never, void

설명: Dart 타입 시스템5에서 자주 보는 타입 몇 가지를 예제로 정리합니다.

Object any = 'text'; // 어떤 객체든 담을 수 있음
dynamic x = 1; // 정적 타입 검사에서 더 느슨한 타입
x = 'now string';
void logIt(Object v) {
print(v);
}
Never fail(String msg) {
throw StateError(msg);
}

주의: dynamic은 강력하지만, 분석기/자동 완성의 도움을 줄이고 실행 중 오류로 이어지기 쉬워서 남용을 피하는 흐름이 자주 제시됩니다.

Comments(주석)//, /* */, ///

설명: 한 줄 주석은 //, 여러 줄 주석은 /* ... */을 사용합니다.
API 문서 주석(documentation comments)6/// 또는 /** ... */ 형태를 씁니다.

// 한 줄 주석
/*
여러 줄 주석
*/
/// 문서 주석: 이 함수가 무엇을 하는지 설명합니다.
int add(int a, int b) => a + b;

주의: 문서 주석은 dart doc로 API 문서를 만들 때 함께 사용됩니다.

Metadata(애노테이션)@something

설명: 메타데이터(metadata)7는 선언에 추가 정보를 붙이는 문법입니다.
대표적으로 @Deprecated@override 같은 애노테이션이 소개됩니다.

class Base {
void run() {}
}
class Child extends Base {
@override
void run() {}
@Deprecated('대신 newRun을 사용하세요.')
void oldRun() {}
void newRun() {}
}

주의: 애노테이션은 “컴파일러/분석기/도구가 참고하는 정보”로 쓰이는 경우가 많습니다.

import / exportimport, as, show/hide

설명: 라이브러리를 불러오려면 import를 사용합니다.
이름 충돌을 피하려면 as로 접두어를 붙이고, 공개 범위를 줄이려면 show/hide를 사용할 수 있습니다.

import 'dart:math' as math;
double area(double r) => math.pi * r * r;
import 'package:my_pkg/my_pkg.dart' show ApiClient;
// import 'package:my_pkg/my_pkg.dart' hide InternalHelper;

주의: import는 “파일 포함”이 아니라 “라이브러리 심볼을 가져오는 것”입니다.

Operators(연산자 모음) — 산술/비교/논리/비트/대입

설명: Dart의 연산자8는 값을 계산하고, 비교하고, 조건을 구성하는 데 쓰입니다.
여기서는 “자주 등장하는 것”을 한 번에 모아둡니다.

// 산술
final a = 7 + 3;
final b = 7 - 3;
final c = 7 * 3;
final d = 7 / 3; // double
final e = 7 ~/ 3; // 정수 나눗셈(몫)
final f = 7 % 3; // 나머지
// 비교/동등
final gt = 3 > 2;
final eq = 3 == 3;
final ne = 3 != 4;
// 논리
final ok = (1 < 2) && (2 < 3);
final nope = (1 < 2) || (2 > 3);
final neg = !(1 < 2);
// 비트/시프트
final and = 0b1100 & 0b1010;
final or = 0b1100 | 0b1010;
final xor = 0b1100 ^ 0b1010;
final shl = 1 << 3;
final shr = 8 >> 2;
// 복합 대입
var x = 1;
x += 2;
x *= 3;

주의: ~/는 “정수 몫”을 만드는 연산자이고, /는 결과가 double입니다.

String interpolation(문자열 보간)${expression} / $identifier

설명: 문자열 안에 값(또는 표현식)을 넣을 때 ${expression}을 사용합니다.
표현식이 단순 식별자(identifier)9라면 {}를 생략해서 $myVar처럼 쓸 수 있습니다.

String stringify(int x, int y) {
return '$x $y';
}

주의: 디버깅 출력이라면 값만 찍기보다 'x=$x y=$y'처럼 “이름+값” 형태가 더 읽기 쉽습니다.

String literals(문자열 리터럴) — raw, multi-line

설명: raw string10r'...'처럼 r을 붙여 백슬래시 이스케이프를 해석하지 않습니다.
여러 줄 문자열(multi-line string)11''' 또는 """로 만들 수 있습니다.

final rawPath = r'C:\\tmp\\file.txt';
final text = '''
첫 줄
둘째 줄
''';

주의: raw string에서는 \n 같은 이스케이프가 그대로 문자로 들어갑니다.

Nullable variables(nullable 변수)T?

설명: Dart는 sound null safety12를 강제합니다.
즉, 기본은 non-nullable이고, null이 가능하면 타입에 ?를 붙여 T?로 선언합니다.

int? a; // 초기값은 null
String? name = 'Jane';
String? address; // 초기값은 null

주의: int a = null;처럼 non-nullable 타입에 null을 넣을 수 없습니다.

Null-aware operators(null-aware 연산자)??, ??=

설명: 값이 null일 수 있을 때, “대체값” 또는 “한 번만 대입”을 간단히 표현합니다.

int? a; // = null
a ??= 3;
print(a); // 3
a ??= 5;
print(a); // 3 (이미 값이 있으니 유지)
String? foo = 'a string';
String? bar; // = null
String? baz = foo ?? bar; // foo가 null이면 bar

주의: ??=는 “현재 값이 null일 때만” 대입합니다.

Conditional property access(조건부 접근)?.

설명: 객체가 null일 수 있을 때, 속성/메서드 접근 전에 ?.를 사용하면 null인 경우 접근을 건너뜁니다.

String? upperCaseIt(String? str) {
return str?.toUpperCase();
}

주의: myObject?.someProperty?.someMethod()처럼 여러 번 이어서 쓸 수 있습니다.

Null assertion(null 단언)expr!

설명: 값이 nullable이지만, “이 지점에서는 null이 아니라고 확신한다”면 !를 붙여 non-nullable로 다룰 수 있습니다.

int lengthOfName(String? name) {
// name이 null이면 runtime error가 납니다.
return name!.length;
}

주의: !는 null이면 실행 중 오류가 나므로, 가능한 경우 if (name == null) return 0; 같은 안전한 처리나 ?./?? 패턴이 더 적합할 수 있습니다.

Type test / cast(타입 테스트/캐스트)is, is!, as

설명: 런타임 타입을 확인하려면 is/is!를 사용합니다.
캐스트가 필요하면 as를 사용합니다.

String describe(Object x) {
if (x is int) {
return 'int: $x';
}
if (x is String) {
return 'String: $x';
}
return 'other';
}
void useAs(Object x) {
final s = x as String; // String이 아니면 runtime error
print(s.toUpperCase());
}

주의: as는 실패 시 실행 중 오류가 날 수 있으므로, 보통 is로 먼저 검사하는 흐름이 함께 등장합니다.

assertassert(condition)

설명: assert13는 “개발 중에만” 조건을 검사하는 용도로 자주 등장합니다.
특히 생성자 initializer list에서 입력 검증 예시로 함께 소개됩니다.

int firstCharCode(String s) {
assert(s.isNotEmpty);
return s.codeUnitAt(0);
}
class NonNegativePoint {
final int x;
final int y;
NonNegativePoint(this.x, this.y) : assert(x >= 0), assert(y >= 0);
}

주의: assert는 런타임 검증을 “항상” 보장하는 장치가 아닐 수 있어, 입력을 반드시 막아야 한다면 예외 처리 같은 방식이 더 적합할 수 있습니다.

Branches(분기)if, switch, ?:, switch expression

설명: 조건에 따라 실행 흐름을 나눌 때 if/else 또는 switch를 씁니다.
값을 고르는 목적이라면 ?:(conditional operator)14switch expression15을 사용할 수 있습니다.

String grade(int score) {
if (score >= 90) {
return 'A';
}
if (score >= 80) {
return 'B';
}
return 'C';
}
String label(bool isOk) => isOk ? 'OK' : 'NO';
String toMessage(String command) {
switch (command) {
case 'OPEN':
return '열기';
case 'CLOSE':
return '닫기';
default:
return '알 수 없음';
}
}
String describeNum(int n) {
// switch expression은 값을 바로 만들 때 쓸 수 있습니다.
return switch (n) {
0 => 'zero',
1 => 'one',
_ => 'many',
};
}

주의: switch expression은 patterns16와 함께 쓰이는 흐름이 많아서, 처음에는 switch 문부터 익히는 편이 덜 헷갈립니다.

Loops(반복)for, for-in, while, do-while, break, continue

설명: 같은 작업을 여러 번 수행할 때 반복문을 씁니다.

int sumTo(int n) {
var sum = 0;
for (var i = 1; i <= n; i += 1) {
sum += i;
}
return sum;
}
String joinLines(List<String> lines) {
var out = '';
for (final line in lines) {
if (line.isEmpty) continue; // 빈 줄은 건너뛰기
out = out.isEmpty ? line : '$out\\n$line';
}
return out;
}
int firstPositive(List<int> nums) {
var i = 0;
while (i < nums.length) {
if (nums[i] > 0) return nums[i];
i += 1;
}
return 0;
}
int tries(int target) {
var n = 0;
do {
n += 1;
if (n == target) break; // 조건을 만족하면 종료
} while (true);
return n;
}

주의: break는 “반복 종료”, continue는 “이번 회차만 건너뛰기”입니다.

Labels(라벨)label:, break label, continue label

설명: 중첩 반복에서 바깥 반복을 제어하고 싶으면 라벨을 사용할 수 있습니다.

int findFirst(List<List<int>> matrix, int target) {
outer:
for (final row in matrix) {
for (final v in row) {
if (v == target) return v;
if (v < 0) continue outer; // 음수를 만나면 다음 row로
}
}
return -1;
}

주의: 라벨은 강력하지만 흐름이 복잡해질 수 있어, 정말 필요한 경우에만 쓰는 흐름이 일반적입니다.

Collection literals(컬렉션 리터럴) — List/Set/Map

설명: List/Set/Map을 리터럴로 만들 수 있고, 타입 추론(type inference)17이 동작합니다.
비어 있는 컬렉션은 타입이 추론되지 않을 수 있어 <T>로 타입을 명시하는 패턴이 함께 쓰입니다.

final aListOfStrings = ['one', 'two', 'three'];
final aSetOfStrings = {'one', 'two', 'three'};
final aMapOfStringsToInts = {'one': 1, 'two': 2, 'three': 3};
final aListOfInts = <int>[];
final aSetOfInts = <int>{};
final aMapOfIntToDouble = <int, double>{};

주의: 하위 타입으로 초기화하더라도 상위 타입 컬렉션으로 만들고 싶다면 <BaseType>[SubType(), ...]처럼 타입을 명시합니다.

Collection if/for(컬렉션 안에서 if/for) — 리터럴 안의 if/for

설명: List/Set/Map 리터럴 안에서 iffor를 쓸 수 있습니다.
조건에 따라 항목을 넣거나, 반복으로 항목을 생성할 때 자주 쓰입니다.

List<String> buildMenu(bool isAdmin) {
return [
'home',
if (isAdmin) 'admin',
'settings',
];
}
List<int> squaresUpTo(int n) {
return [
for (var i = 1; i <= n; i += 1) i * i,
];
}
Map<String, int> indexOf(List<String> items) {
return {
for (var i = 0; i < items.length; i += 1) items[i]: i,
};
}

주의: 컬렉션 리터럴 안의 if/for는 “값을 만든 뒤 필터링”보다, “만드는 순간부터 조건/반복을 적용”하는 느낌이라 코드가 짧아지는 경우가 많습니다.

Spread(스프레드)..., ...?

설명: 다른 컬렉션의 원소를 “펼쳐서” 넣을 때 ...를 사용합니다.
nullable 컬렉션이면 ...?로 null일 때는 아무 것도 추가하지 않는 패턴이 소개됩니다.

List<int> merge(List<int> a, List<int> b) {
return [
...a,
...b,
];
}
List<String> safeMerge(List<String>? maybe, List<String> base) {
return [
...base,
...?maybe,
];
}

주의: ...은 “원소 단위로 복사해 넣는 표현”입니다. (원본 리스트를 그대로 참조하는 의미가 아닙니다.)

Arrow syntax(화살표 문법)=>

설명: =>는 오른쪽 표현식을 평가해 반환하는 “한 줄 함수” 문법입니다.

bool hasEmpty = ['one', '', 'three'].any((s) => s.isEmpty);
String joinWithCommas(List<String> strings) => strings.join(',');

주의: => 오른쪽에는 “표현식”이 와야 합니다. (블록 { ... }를 넣는 문법이 아닙니다.)

Function parameters(함수 파라미터) — required/optional/named

설명: 파라미터는 크게 positional과 named로 나뉘고, optional 여부/기본값/required 여부를 조합해 설계합니다.

// required positional
int add(int a, int b) => a + b;
// optional positional
int sumUpToFive(int a, [int b = 2, int c = 3, int d = 4, int e = 5]) {
return a + b + c + d + e;
}
// named (기본값/nullable/required 조합)
String greet({required String name, String? title, String suffix = '님'}) {
final t = title == null ? '' : '$title ';
return '$t$name$suffix';
}

주의: “옵션 위치([])”와 “named({})”는 동시에 섞을 수 없습니다.

Anonymous functions & closures(익명 함수/클로저)

설명: 함수는 값으로 다룰 수 있고, 익명 함수(람다)를 변수에 담아 전달할 수 있습니다.
또한 바깥 스코프의 변수를 캡처(capture)18하는 형태를 클로저(closure)19라고 부릅니다.

int apply(int x, int Function(int) f) => f(x);
void main() {
final doubled = apply(10, (n) => n * 2);
print(doubled); // 20
}
Function makeCounter() {
var count = 0;
return () {
count += 1;
return count;
};
}
void main() {
final c = makeCounter();
print(c()); // 1
print(c()); // 2
}

주의: 클로저가 외부 변수를 오래 잡고 있으면, 기대보다 오래 살아남는 상태가 생길 수 있습니다.

Generators(제너레이터)sync*, yield, yield*

설명: 제너레이터(generator)20는 값을 “여러 번” 만들어 내는 함수 패턴입니다.
동기 시퀀스는 sync*, 비동기 시퀀스는 async*와 함께 yield를 사용합니다.

Iterable<int> oddsUpTo(int n) sync* {
for (var i = 1; i <= n; i += 2) {
yield i;
}
}
Iterable<int> concat(Iterable<int> a, Iterable<int> b) sync* {
yield* a;
yield* b;
}

주의: yield*는 다른 이터러블/스트림의 값을 “그대로 이어서” 내보내는 문법입니다.

Typedefs(타입 별칭)typedef

설명: 함수 타입이나 복잡한 타입에 “이름”을 붙여 재사용할 수 있습니다.

typedef IntMapper = int Function(int);
int apply(IntMapper f, int x) => f(x);
typedef Json = Map<String, Object?>;

주의: callable objects21처럼 “함수처럼 호출되는 객체”와 함께 등장하는 경우도 있습니다.

Cascades.., ?..

설명: 같은 객체에 대한 여러 작업을 한 번에 이어서 쓰고 싶을 때 ..를 사용합니다.
객체가 null일 수 있으면 ?..(null-shorting cascade)을 시작점으로 사용해 null일 때는 전체 작업을 건너뜁니다.

class BigObject {
int anInt = 0;
String aString = '';
List<double> aList = [];
bool _done = false;
void allDone() => _done = true;
}
BigObject fillBigObject(BigObject obj) {
return obj
..anInt = 1
..aString = 'String!'
..aList.add(3)
..allDone();
}

주의: cascades의 “결과”는 마지막 호출의 반환값이 아니라, 원래 객체 참조입니다.

Getters and setters(getter/setter)get, set

설명: 단순 필드 대신 “검증/계산”을 끼워 넣고 싶을 때 getter와 setter를 정의합니다.

class ShoppingCart {
List<double> _prices = [];
double get total => _prices.fold(0, (e, t) => e + t);
set prices(List<double> value) {
if (value.any((p) => p < 0)) {
throw InvalidPriceException();
}
_prices = value;
}
}
class InvalidPriceException {}

주의: setter에서 검증에 실패하면 예외를 던질 수 있습니다.

Optional positional parameters(옵션 위치 파라미터)[]

설명: 위치 기반 파라미터를 옵션으로 만들려면 []로 감쌉니다.
옵션 위치 파라미터는 항상 파라미터 목록의 뒤에 오고, 기본값을 지정하지 않으면 default는 null입니다.

String joinWithCommas(int a, [int? b, int? c, int? d, int? e]) {
var total = '$a';
if (b != null) total = '$total,$b';
if (c != null) total = '$total,$c';
if (d != null) total = '$total,$d';
if (e != null) total = '$total,$e';
return total;
}

주의: 옵션 파라미터를 쓸 때는 null 체크가 자연스럽게 따라옵니다.

Named parameters(이름 있는 파라미터){}, required

설명: 파라미터를 이름으로 전달하고 싶으면 {}로 named parameters를 정의합니다.
named parameters는 기본적으로 optional이며, 필요하면 required로 강제할 수 있습니다.

void printName(String firstName, String lastName, {String? middleName}) {
print('$firstName ${middleName ?? ''} $lastName');
}
class MyDataObject {
final int anInt;
final String aString;
final double aDouble;
MyDataObject({this.anInt = 1, this.aString = 'Old!', this.aDouble = 2.0});
MyDataObject copyWith({int? newInt, String? newString, double? newDouble}) {
return MyDataObject(
anInt: newInt ?? anInt,
aString: newString ?? aString,
aDouble: newDouble ?? aDouble,
);
}
}

주의: 한 함수에서 “옵션 위치 파라미터([])”와 “named parameters({})”를 동시에 가질 수 없습니다.

Records(a, b), (x: 1, y: 2)

설명: records22는 여러 값을 “한 덩어리”로 묶어 전달/반환할 때 쓰는 문법입니다.
positional 형태와 named 형태를 모두 가질 수 있습니다.

(int, int) swap((int, int) pair) {
final (a, b) = pair;
return (b, a);
}
({int x, int y}) moveRight(({int x, int y}) point) {
return (x: point.x + 1, y: point.y);
}

주의: final (a, b) = pair;처럼 record를 “풀어서 받는” 문법은 patterns16 흐름과 함께 등장합니다.

Generics(제네릭)<T>

설명: generics23는 “타입을 파라미터처럼 받는” 문법입니다.
List<String>처럼 컬렉션의 원소 타입을 고정하거나, class Box<T>처럼 재사용 가능한 타입을 만들 때 사용합니다.

class Box<T> {
final T value;
const Box(this.value);
}
Box<int> a = const Box(1);
Box<String> b = const Box('ok');
T first<T>(List<T> items) => items.first;

주의: 빈 컬렉션을 만들 때 <T>[]처럼 타입을 명시하는 패턴도 generics의 한 형태입니다.

Classes(클래스 기본)class, abstract class, extends, implements, with

설명: 클래스는 상태(필드)와 동작(메서드)을 묶는 기본 단위입니다.
상속은 extends, 인터페이스 구현은 implements, 믹스인은 with를 사용합니다.

abstract class Animal {
void speak();
}
mixin Walker {
void walk() {
print('걷는다');
}
}
class Dog extends Animal with Walker {
@override
void speak() {
print('멍멍');
}
}
class RobotDog implements Animal {
@override
void speak() {
print('삐빅');
}
}

주의: implements는 “구현을 상속받는 것”이 아니라 “인터페이스를 만족하도록 직접 구현하는 것”입니다.

Constructors(생성자 모음) — default/named/factory/redirecting/const

설명: 생성자는 인스턴스를 만드는 방법(초기화 흐름)을 정의합니다.
이 문서(치트시트)는 “자주 등장하는 형태”만 한 번에 모아둡니다.

class Point {
final int x;
final int y;
// default constructor + this. 파라미터
const Point(this.x, this.y);
// named constructor + initializer list
Point.origin() : x = 0, y = 0;
// redirecting constructor
const Point.square(int v) : this(v, v);
// factory constructor
factory Point.fromJson(Map<String, int> json) {
return Point(json['x']!, json['y']!);
}
}

주의: initializer list24final 필드 초기화나 assert 같은 사전 검증에 자주 사용됩니다.

static(정적 멤버)

설명: static 멤버는 인스턴스가 아니라 클래스에 속합니다.

class Counter {
static int created = 0;
int value = 0;
Counter() {
created += 1;
}
}
void main() {
Counter();
Counter();
print(Counter.created); // 2
}

주의: static은 공유 상태(shared state)가 되기 쉬워서, 테스트/동시성에서 주의가 필요할 수 있습니다.

Privacy(프라이빗/가시성)_name

설명: 식별자 앞에 _를 붙이면 라이브러리 단위로 private25가 됩니다.

class BankAccount {
int _balance = 0;
void deposit(int amount) {
_balance += amount;
}
}

주의: Dart의 private는 “클래스 단위”가 아니라 “라이브러리 단위”라는 흐름이 자주 강조됩니다.

@override & super(오버라이드)

설명: 상속 관계에서 메서드를 재정의할 때 @override를 사용합니다.
부모 구현을 호출하려면 super.method()를 사용할 수 있습니다.

class Base {
String greet() => 'hi';
}
class Child extends Base {
@override
String greet() => '${super.greet()}!';
}

주의: @override는 문법 오류를 막고(의도한 오버라이드인지) 분석기 경고를 줄이는 데 도움을 줍니다.

operator / calloperator +, call()

설명: 클래스는 일부 연산자를 오버로드(operator overloading)26할 수 있습니다.
또한 call() 메서드를 정의하면 인스턴스를 함수처럼 호출(callable objects)21할 수 있습니다.

class Vec2 {
final int x;
final int y;
const Vec2(this.x, this.y);
Vec2 operator +(Vec2 other) => Vec2(x + other.x, y + other.y);
}
class Adder {
final int delta;
const Adder(this.delta);
int call(int x) => x + delta;
}
void main() {
final add10 = Adder(10);
print(add10(5)); // 15
}

주의: operator 오버로드는 “읽는 사람에게 자연스럽게 이해되는 경우”에만 쓰는 흐름이 안전합니다.

Mixinsmixin, mixin class

설명: 믹스인은 “다중 상속 없이 기능을 섞는” 방식으로 소개됩니다.
mixin은 믹스인 정의, with는 적용입니다.

mixin LoggerMixin {
void log(String msg) {
print('LOG: $msg');
}
}
class Service with LoggerMixin {
void run() {
log('start');
}
}

주의: 믹스인은 상속과 달리 “클래스 계층을 깊게 만들지 않고” 기능을 조합하는 데 쓰입니다.

Enumsenum

설명: enum은 고정된 값 집합을 표현합니다.

enum Status { ready, running, done }
String label(Status s) {
return switch (s) {
Status.ready => '준비',
Status.running => '진행',
Status.done => '완료',
};
}

주의: switch에서 enum을 다루면, 경우를 빠뜨렸을 때 경고/에러가 도움을 줄 수 있습니다.

Extension methodsextension

설명: 기존 타입에 메서드를 “추가한 것처럼” 보이게 하려면 extension methods를 사용합니다.

extension StringX on String {
bool get isBlank => trim().isEmpty;
}
bool ok(String s) => !s.isBlank;

주의: 같은 이름의 extension이 여러 개 있으면, 어떤 것이 적용되는지 헷갈릴 수 있습니다.

Extension typesextension type

설명: extension type은 “기존 표현을 감싸서 새 타입처럼 보이게” 만들되, 런타임 오버헤드를 줄이는 방향의 기능으로 소개됩니다.

extension type EmailAddress(String value) {
bool get isValid => value.contains('@');
}
void main() {
final email = EmailAddress('[email protected]');
print(email.isValid);
}

주의: extension type은 런타임에서 “별도 래퍼 객체가 없을 수 있다”는 점이 함께 설명됩니다.

Class modifiersbase, interface, final, sealed

설명: class modifiers는 “다른 라이브러리/패키지에서 이 타입을 어떻게 재사용할 수 있는지”를 제한해 API 안정성을 높이는 흐름으로 소개됩니다.

// 예시: 같은 패키지 안에서만 상속 가능(개념 예시)
base class BaseApi {}
// 예시: 구현은 가능하지만 상속은 제한(개념 예시)
interface class ApiShape {}
// 예시: 상속/구현을 막아서 확장 포인트를 닫음(개념 예시)
final class Closed {}
// 예시: 같은 라이브러리에서만 서브타입을 허용(개념 예시)
sealed class Result {}

주의: modifiers는 “문법”이지만, 실제로는 “API 설계 규칙”과 함께 다뤄지는 경우가 많습니다.

Patternscase, destructure, if-case

설명: patterns16는 값을 “형태로 매칭”하는 문법입니다.
예를 들어 record나 list/map 같은 구조에서 일부를 꺼내거나, 특정 형태일 때만 분기하는 흐름이 소개됩니다.

String describePair((int, int) p) {
return switch (p) {
(0, 0) => 'origin',
(final x, 0) => 'x=$x on x-axis',
(0, final y) => 'y=$y on y-axis',
_ => 'other',
};
}
void ifCase(Object value) {
if (value case (int x, int y)) {
print('record: x=$x y=$y');
}
}

주의: patterns는 종류가 많습니다. (list/map/record/object patterns 등)
처음에는 “record 분해 + switch/if-case” 정도만 익히고, 필요한 패턴 타입은 pattern types27에서 추가로 확인하는 흐름이 안전합니다.

Exceptions(예외)try / on / catch / rethrow / finally

설명: 예외를 던질 수 있고(throw), 처리할 때는 try/on/catch를 사용합니다.
완전히 처리할 수 없으면 rethrow로 다시 던지고, 무조건 실행해야 하는 정리는 finally에 둡니다.

try {
breedMoreLlamas();
} on OutOfLlamasException {
buyMoreLlamas();
} on Exception catch (e) {
print('알 수 없는 예외: $e');
} catch (e) {
print('정말 알 수 없는 값: $e');
}
try {
breedMoreLlamas();
} catch (e) {
print('예외가 발생했지만, 여기서는 로그만 남긴다.');
rethrow;
} finally {
cleanLlamaStalls();
}

주의: “모든 예외가 반드시 잡혀야 한다”는 규칙은 없습니다. (unchecked)

async/awaitFuture, async, await

설명: 시간이 걸리는 작업(네트워크/파일/타이머 등)을 다룰 때 Future28async/await29를 사용합니다.
awaitFuture가 끝날 때까지 “현재 함수의 다음 줄”을 잠시 멈추고 결과를 받습니다.

Future<String> loadUserName() async {
// 예시를 단순화하기 위해 즉시 완료되는 Future를 사용합니다.
final name = await Future.value('Jane');
return 'user=$name';
}

주의: awaitasync 함수 안에서만 쓸 수 있습니다.

StreamStream, async*, yield

설명: Future가 “한 번의 결과”라면, Stream30은 “여러 번의 값(이벤트)”을 시간에 따라 전달합니다.
값을 여러 번 내보내는 함수는 async*yield를 함께 쓰는 패턴이 소개됩니다.

Stream<int> countUpTo(int n) async* {
for (var i = 1; i <= n; i += 1) {
yield i;
}
}
Future<int> sumStream(Stream<int> s) async {
var sum = 0;
await for (final v in s) {
sum += v;
}
return sum;
}

주의: await for는 stream 이벤트를 순서대로 처리하는 문법이고, 이 역시 async 함수 안에서 사용합니다.

Using this in a constructor(생성자에서 this. 사용)

설명: 생성자 파라미터로 받은 값을 필드에 바로 대입할 때 this.fieldName을 사용할 수 있습니다.
named parameters에서도 같은 방식이 가능하고, 기본값이 있으면 required를 생략할 수 있습니다.

class MyColor {
int red;
int green;
int blue;
MyColor(this.red, this.green, this.blue);
}
class MyColor {
int red;
int green;
int blue;
MyColor({required this.red, required this.green, required this.blue});
}
class MyColor {
int red;
int green;
int blue;
MyColor([this.red = 0, this.green = 0, this.blue = 0]);
}

주의: required는 “null이 될 수 없는 값”을 필수로 받도록 만드는 용도로 자주 등장합니다.

Initializer lists(initializer list): 뒤에서 초기화

설명: 생성자 본문이 실행되기 전에 해야 하는 초기화는 initializer list에 둡니다.
또한 개발 중에만 체크할 조건은 assert로 둘 수 있습니다.

class FirstTwoLetters {
final String letterOne;
final String letterTwo;
FirstTwoLetters(String word)
: assert(word.length >= 2),
letterOne = word[0],
letterTwo = word[1];
}

주의: final 필드는 생성자 본문 전에 값이 결정되어야 하므로 initializer list가 특히 유용합니다.

Named constructors(이름 있는 생성자)Class.name()

설명: 한 클래스에 여러 생성자를 두고 싶을 때, Class.name() 형태의 named constructor를 정의합니다.

class Point {
double x, y;
Point(this.x, this.y);
Point.origin() : x = 0, y = 0;
}
final p = Point.origin();

주의: named constructor에서도 initializer list를 사용할 수 있습니다.

Factory constructors(factory 생성자)factory

설명: factory 생성자는 “항상 같은 클래스 인스턴스를 만든다”는 제약이 없습니다.
즉, subtype을 반환하거나, 입력에 따라 다른 구현을 반환하는 패턴이 가능합니다.

class Square extends Shape {}
class Circle extends Shape {}
class Shape {
Shape();
factory Shape.fromTypeName(String typeName) {
if (typeName == 'square') return Square();
if (typeName == 'circle') return Circle();
throw ArgumentError('Unrecognized $typeName');
}
}

주의: 입력이 예상 범위를 벗어나면 예외를 던지는 흐름이 자주 함께 나옵니다.

Redirecting constructors(리다이렉트 생성자): this(...)

설명: 생성자의 목적이 “다른 생성자로 위임”인 경우, 본문 없이 : 뒤에서 this(...)로 넘깁니다.

class Automobile {
String make;
String model;
int mpg;
Automobile(this.make, this.model, this.mpg);
Automobile.hybrid(String make, String model) : this(make, model, 60);
Automobile.fancyHybrid() : this.hybrid('Futurecar', 'Mark 2');
}
class Color {
int red;
int green;
int blue;
Color(this.red, this.green, this.blue);
Color.black() : this(0, 0, 0);
}

주의: 리다이렉트 생성자는 “중복 초기화”를 줄이는 데 유용합니다.

Const constructors(const 생성자)const + final

설명: 인스턴스가 바뀌지 않는(immutable) 클래스라면 const 생성자를 만들어 컴파일 타임 상수로 사용할 수 있습니다.
이를 위해 인스턴스 변수는 final이어야 합니다.

class ImmutablePoint {
static const ImmutablePoint origin = ImmutablePoint(0, 0);
final int x;
final int y;
const ImmutablePoint(this.x, this.y);
}
class Recipe {
final List<String> ingredients;
final int calories;
final double milligramsOfSodium;
const Recipe(this.ingredients, this.calories, this.milligramsOfSodium);
}

주의: const로 만든 리스트는 수정할 수 없어서, add 같은 호출에서 오류가 나는 흐름이 함께 제시됩니다.


Footnotes#

  1. final(final): 변수에 값을 “한 번만” 대입하도록 제한하는 키워드다.

  2. const(const): 컴파일 타임에 값이 결정되는 상수를 선언하는 키워드다.

  3. late(late): 변수를 “나중에 초기화”하도록 선언하는 키워드다.

  4. built-in types(기본 타입): Dart가 기본으로 제공하는 핵심 타입(예: bool, int, double, num, String 등)이다.

  5. type system(타입 시스템): 값과 타입의 관계, 검사 규칙, null safety 등을 포함한 타입 규칙 체계다.

  6. documentation comments(문서 주석): /// 또는 /** ... */로 작성하며, API 문서 생성 도구가 읽을 수 있는 주석 형식이다.

  7. metadata(메타데이터): 선언에 @Something 형태로 붙여 추가 정보를 제공하는 문법이다.

  8. operators(연산자): 값의 계산/비교/결합 등을 표현하는 기호 및 키워드 집합이다.

  9. identifier(식별자): 변수/함수/클래스 이름처럼 프로그램 요소를 가리키는 이름이다.

  10. raw string(raw 문자열): r'...' 형태로, 이스케이프 시퀀스를 처리하지 않고 그대로 문자열로 취급하는 문자열 리터럴이다.

  11. multi-line string(여러 줄 문자열): ''' 또는 """로 여러 줄에 걸친 문자열을 만드는 문법이다.

  12. sound null safety(사운드 널 안전성): null이 가능한지 타입 수준에서 구분하고, null 관련 오류를 컴파일/분석 단계에서 줄이도록 강제하는 모델이다.

  13. assert(assert): 개발 중 조건을 검사하고, 실패 시 오류를 내는 디버그용 검사 문법이다.

  14. conditional operator(조건 연산자): condition ? a : b 형태로 조건에 따라 값을 선택하는 표현이다.

  15. switch expression(switch 표현식): switch (...) { ... } 형태로 값을 만들어 반환하는 표현식이다.

  16. patterns(패턴): 값의 형태를 기준으로 매칭하고, 필요한 부분을 꺼내는 문법/규칙 집합이다. 2 3

  17. type inference(타입 추론): 명시 타입 없이도 컴파일러가 값과 문맥으로 타입을 결정하는 기능이다.

  18. capture(캡처): 함수가 자신이 정의된 바깥 스코프의 변수를 “사용(참조)”하는 것을 말한다.

  19. closure(클로저): 캡처된 변수(환경)18를 포함해, 함수가 바깥 스코프의 상태를 함께 유지하는 형태다.

  20. generators(제너레이터): sync*/async*yield로 값을 여러 번 생성해 내보내는 함수 패턴이다.

  21. callable objects(호출 가능한 객체): 인스턴스에 call()을 정의해 함수처럼 호출할 수 있는 객체다. 2

  22. records(records): 여러 값을 하나로 묶어 전달/반환할 수 있는 값 타입 문법이다.

  23. generics(제네릭): 타입을 파라미터처럼 받아 재사용 가능한 함수/클래스를 만들 수 있게 하는 문법이다.

  24. initializer list(초기화 리스트): 생성자 본문 실행 전에 : 뒤에서 필드를 초기화하거나 assert를 실행하는 구간이다.

  25. privacy(프라이빗/가시성): _name처럼 밑줄로 시작하는 식별자를 라이브러리 밖에서 숨기는 규칙이다.

  26. operator overloading(연산자 오버로딩): 클래스에서 operator + 같은 연산자 메서드를 정의해 연산자 동작을 커스터마이즈하는 기능이다.

  27. pattern types(패턴 타입): patterns에서 사용할 수 있는 패턴 종류(list/map/record/object 등)를 분류해 설명한 자료다.

  28. Future(Future): 나중에 완료될 “한 번의 결과”를 나타내는 타입이다.

  29. async/await(async/await): 비동기 작업을 순서대로 읽히는 코드처럼 작성하게 해주는 키워드 조합이다.

  30. Stream(Stream): 시간에 따라 여러 값을 순차적으로 전달하는 비동기 시퀀스 타입이다.

공유

이 글이 도움이 되었다면 다른 사람과 공유해주세요!

Dart 튜토리얼 36편: Dart Cheatsheet(핵심 문법 빠르게 복습)
https://moodturnpost.net/posts/dart/dart-cheatsheet/
작성자
Moodturn
게시일
2026-01-04
Moodturn

목차