Dart Type System
Static typing, type inference, generics, typedef
Dart is statically typed with type inference support. Use is/as for type checking and casting, with automatic type promotion after is checks. Generics enable type-safe collections and classes, while typedef lets you name complex function types.
κΈ°λ³Έ μ 곡 νμ
Dartλ μ μ νμ μΈμ΄λ‘, μ»΄νμΌ μκ°μ νμ κ²μ¬λ₯Ό μνν©λλ€. μλλ Dartκ° κΈ°λ³ΈμΌλ‘ μ 곡νλ νμ λ€μ λλ€.
int integer = 42;
double decimal = 3.14;
num number = 10; // intμ doubleμ μμ νμ
String text = 'μλ
νμΈμ';
bool flag = true;
List<int> numbers = [1, 2, 3];
Map<String, dynamic> person = {'name': 'νκΈΈλ', 'age': 30};
Set<String> uniqueNames = {'νκΈΈλ', 'κΉμ² μ', 'μ΄μν¬'};
Symbol symbol = #symbolName;
νΉμ νμ : void, dynamic, Object
void printMessage() {
print('λ©μμ§ μΆλ ₯');
}
dynamic dynamicValue = 'λ¬Έμμ΄';
dynamicValue = 42; // λ€λ₯Έ νμ
μ¬ν λΉ κ°λ₯
Object objectValue = 'Hello';
dynamic vs Object
dynamicμ νμ
κ²μ¬λ₯Ό μμ ν 건λλλλ€. Objectλ λͺ¨λ Dart κ°μ²΄μ μ΅μμ νμ
μ΄μ§λ§ λ©μλ νΈμΆ μ νμ
κ²μ¬κ° μ΄λ£¨μ΄μ§λλ€.
νμ μΆλ‘ (Type Inference)
varλ‘ μ μΈνλ©΄ μ΄κΈ°κ°μμ νμ
μ΄ μλ κ²°μ λλ©°, μ΄ν λ€λ₯Έ νμ
μΌλ‘ λ³κ²½ν μ μμ΅λλ€.
var name = 'νκΈΈλ'; // StringμΌλ‘ μΆλ‘
var age = 30; // intλ‘ μΆλ‘
var height = 175.5; // doubleλ‘ μΆλ‘
var active = true; // boolλ‘ μΆλ‘
var items = [1, 2, 3]; // List<int>λ‘ μΆλ‘
var getName = () {
return 'νκΈΈλ'; // λ°ν νμ
λ μΆλ‘
};
var people = [ // List<Map<String, Object>>λ‘ μΆλ‘
{'name': 'νκΈΈλ', 'age': 30},
{'name': 'κΉμ² μ', 'age': 25},
];
νμ μ²΄ν¬ & μΊμ€ν
is / is! μ°μ°μλ‘ νμ
μ νμΈνλ©΄, ν΄λΉ λΈλ‘ μμμ μλμΌλ‘ νμ
νλ‘λͺ¨μ
μ΄ μ μ©λ©λλ€.
Object value = 'λ¬Έμμ΄';
if (value is String) {
// μ΄ λΈλ‘ μμμ μλμΌλ‘ StringμΌλ‘ μΊμ€ν
λ¨
print('λ¬Έμμ΄ κΈΈμ΄: ${value.length}');
}
if (value is! int) {
print('μ μκ° μλλλ€');
}
as μ°μ°μλ λͺ
μμ μΊμ€ν
μ΄λ©°, μ€ν¨ μ λ°νμ μλ¬κ° λ°μν©λλ€.
Object value = 'λ¬Έμμ΄';
String text = value as String;
print(text.toUpperCase());
// int number = value as int; // λ°νμ μλ¬!
νμ νλ‘λͺ¨μ (Type Promotion)
is κ²μ¬ μ΄ν ν΄λΉ λΈλ‘ μμμ μλμΌλ‘ νμ
μ΄ μΉκ²©λ©λλ€. λΈλ‘ λ°κΉ₯μμλ μλ νμ
μΌλ‘ λμκ°λλ€.
Object value = 'μλ
νμΈμ';
if (value is String) {
// μλμΌλ‘ StringμΌλ‘ μΉκ²©
print('λλ¬Έμ: ${value.toUpperCase()}');
print('κΈΈμ΄: ${value.length}');
}
// print(value.length); // μλ¬: Objectμλ lengthκ° μμ
μ λ€λ¦ ν΄λμ€ (Generic Classes)
νμ μ λ§€κ°λ³μλ‘ λ°μ μ¬μ¬μ© κ°λ₯ν ν΄λμ€λ₯Ό μμ±ν μ μμ΅λλ€.
class Box<T> {
T value;
Box(this.value);
T getValue() {
return value;
}
void setValue(T newValue) {
value = newValue;
}
}
void main() {
var stringBox = Box<String>('μλ
νμΈμ');
print(stringBox.getValue()); // 'μλ
νμΈμ'
var intBox = Box<int>(42);
print(intBox.getValue()); // 42
var doubleBox = Box(3.14); // Box<double>λ‘ μΆλ‘
}
μ λ€λ¦ ν¨μ (Generic Functions)
T first<T>(List<T> items) {
return items.first;
}
void main() {
var names = ['νκΈΈλ', 'κΉμ² μ', 'μ΄μν¬'];
var firstString = first<String>(names);
print(firstString); // 'νκΈΈλ'
var numbers = [1, 2, 3, 4, 5];
var firstInt = first(numbers); // Tκ° intλ‘ μΆλ‘
print(firstInt); // 1
}
μ λ€λ¦ νμ μ ν (Type Constraints)
extendsλ₯Ό μ¬μ©νμ¬ μ λ€λ¦ νμ
μ μ νμ κ±Έ μ μμ΅λλ€.
class NumberBox<T extends num> {
T value;
NumberBox(this.value);
void square() {
print(value * value);
}
}
void main() {
var intBox = NumberBox<int>(10);
intBox.square(); // 100
var doubleBox = NumberBox<double>(2.5);
doubleBox.square(); // 6.25
// var stringBox = NumberBox<String>('μ€λ₯'); // μ»΄νμΌ μλ¬!
}
λ€μ€ μ λ€λ¦ λ§€κ°λ³μ & μ λ€λ¦ μμ
class Pair<K, V> {
K first;
V second;
Pair(this.first, this.second);
}
// μ λ€λ¦ ν΄λμ€λ₯Ό μμνμ¬ νμ
κ³ μ
class IntBox extends Box<int> {
IntBox(int value) : super(value);
void increment() {
setValue(getValue() + 1);
}
}
// νμ
λ³μΉ
typedef StringList = List<String>;
typedef KeyValueMap<K, V> = Map<K, V>;
컬λ μ κ³Ό μ λ€λ¦
List, Map, Setμ λͺ¨λ μ λ€λ¦μ νμ©νμ¬ νμ μμ νκ² μ¬μ©ν μ μμ΅λλ€.
// List
List<String> names = ['νκΈΈλ', 'κΉμ² μ', 'μ΄μν¬'];
var fruits = <String>['μ¬κ³Ό', 'λ°λλ', 'μ€λ μ§'];
var numbers = List<int>.filled(5, 0); // [0, 0, 0, 0, 0]
var evens = List<int>.generate(5, (i) => i * 2); // [0, 2, 4, 6, 8]
var filteredNames = names.where((name) => name.length > 2).toList();
var mappedScores = [90, 85, 95].map((score) => score * 1.1).toList();
// Map
Map<String, int> ages = {
'νκΈΈλ': 30,
'κΉμ² μ': 25,
'μ΄μν¬': 28,
};
var config = Map<String, dynamic>();
config['debug'] = true;
config['timeout'] = 30;
// Set
Set<String> uniqueNames = {'νκΈΈλ', 'κΉμ² μ', 'μ΄μν¬'};
var colors = <String>{'λΉ¨κ°', 'νλ', 'λ
Ήμ'};
var nums = Set<int>.from([1, 2, 3, 3, 4]); // {1, 2, 3, 4}
typedef β νμ λ³μΉ & ν¨μ νμ
볡μ‘ν ν¨μ νμ μ΄λ νμ μ μ΄λ¦μ λΆμ¬νμ¬ μ½λ κ°λ μ±μ λμΌ μ μμ΅λλ€.
// ν¨μ νμ
λ³μΉ
typedef IntOperation = int Function(int a, int b);
int add(int a, int b) => a + b;
int subtract(int a, int b) => a - b;
void calculate(IntOperation operation, int x, int y) {
print('κ²°κ³Ό: ${operation(x, y)}');
}
void main() {
calculate(add, 10, 5); // κ²°κ³Ό: 15
calculate(subtract, 10, 5); // κ²°κ³Ό: 5
}
// Dart 2.13+ μΌλ° νμ
λ³μΉ
typedef StringList = List<String>;
typedef UserInfo = Map<String, dynamic>;
void printNames(StringList names) {
for (var name in names) {
print(name);
}
}
void displayUserInfo(UserInfo user) {
print('μ΄λ¦: ${user[\'name\']}, λμ΄: ${user[\'age\']}');
}
Dart 3 ν¨ν΄ λ§€μΉκ³Ό νμ
Dart 3μμλ switch ννμμμ νμ
κΈ°λ° ν¨ν΄ λ§€μΉμ΄ κ°λ₯ν©λλ€.
Object value = 'λ¬Έμμ΄';
switch (value) {
case String():
print('λ¬Έμμ΄: $value');
case int():
print('μ μ: $value');
default:
print('κΈ°ν νμ
: $value');
}
μ€μ μμ : μ λ€λ¦ μΊμ ν΄λμ€
class Cache<T> {
final Map<String, T> _cache = {};
T? get(String key) => _cache[key];
void set(String key, T value) {
_cache[key] = value;
}
bool has(String key) => _cache.containsKey(key);
void remove(String key) => _cache.remove(key);
void clear() => _cache.clear();
}
void main() {
var stringCache = Cache<String>();
stringCache.set('greeting', 'μλ
νμΈμ');
print(stringCache.get('greeting')); // 'μλ
νμΈμ'
var userCache = Cache<Map<String, dynamic>>();
userCache.set('user1', {'name': 'νκΈΈλ', 'age': 30});
var user = userCache.get('user1');
print('μ¬μ©μ: ${user?[\'name\']}, λμ΄: ${user?[\'age\']}');
}
μ€μ μμ : Result νμ ν¨ν΄
μ λ€λ¦μ νμ©νμ¬ μ±κ³΅/μ€ν¨λ₯Ό νμ μμ νκ² μ²λ¦¬νλ Result ν¨ν΄μ λλ€.
abstract class Result<S, E> {
factory Result.success(S value) = Success<S, E>;
factory Result.failure(E error) = Failure<S, E>;
bool get isSuccess;
bool get isFailure;
S? get value;
E? get error;
void when({
required void Function(S value) success,
required void Function(E error) failure,
});
}
class Success<S, E> extends Result<S, E> {
final S _value;
Success(this._value);
@override bool get isSuccess => true;
@override bool get isFailure => false;
@override S get value => _value;
@override E? get error => null;
@override
void when({
required void Function(S value) success,
required void Function(E error) failure,
}) => success(_value);
}
class Failure<S, E> extends Result<S, E> {
final E _error;
Failure(this._error);
@override bool get isSuccess => false;
@override bool get isFailure => true;
@override S? get value => null;
@override E get error => _error;
@override
void when({
required void Function(S value) success,
required void Function(E error) failure,
}) => failure(_error);
}
μ¬μ©λ²:
Result<String, Exception> fetchData() {
try {
return Result.success('λ°μ΄ν°');
} catch (e) {
return Result.failure(Exception('μ€λ₯: $e'));
}
}
void main() {
var result = fetchData();
result.when(
success: (data) => print('μ±κ³΅: $data'),
failure: (error) => print('μ€ν¨: $error'),
);
}
μ°Έκ³ : fpdart ν¨ν€μ§
λ νλΆν ν¨μν νλ‘κ·Έλλ° κΈ°λ₯μ΄ νμνλ€λ©΄ fpdart ν¨ν€μ§λ₯Ό μ°Έκ³ νμΈμ. Either, Option λ± κ°λ ₯ν νμ μ μ 곡ν©λλ€.
νμ μμ€ν λΉκ΅ ν
| ν€μλ | νμ κ²μ¬ | μ©λ |
|---|---|---|
| var | μ»΄νμΌ νμ μΆλ‘ | νμ μΆλ‘ (λ³κ²½ λΆκ°) |
| dynamic | μμ (λ°νμ) | μ무 νμ μ΄λ ν λΉ κ°λ₯ |
| Object | μ»΄νμΌ νμ | λͺ¨λ νμ μ μμ νμ |
| is / is! | λ°νμ μ²΄ν¬ | νμ νμΈ + νλ‘λͺ¨μ |
| as | λ°νμ μΊμ€νΈ | λͺ μμ νμ μΊμ€ν |
| T extends X | μ»΄νμΌ νμ | μ λ€λ¦ νμ μ ν |
Implementation Steps
Type inference β var infers type from initial value, cannot reassign to different type
is/is! and type promotion β inside if (obj is String), String methods are auto-available
Generics β List<T>, Map<K,V> for type-safe collections, extends for type constraints
typedef β name complex function types, typedef Compare<T> = int Function(T a, T b)
Pros
- ✓ Compile-time type checking prevents runtime errors
- ✓ Type promotion allows direct use after is check without casting
Cons
- ✗ Overusing dynamic breaks type safety