快速入门 Dart 语言

基础

变量和常量

声明方式 可变性 可空 赋值时机 类型固定 典型用途
var 可变 由初始化决定 声明时 局部变量,类型显而易见
Type (显式) 可变 默认不可空 使用前 公共 API、需要明确类型
Type? 可变 可空 使用前 可能缺失的值
late Type 可变 不可空 首次使用前 延迟初始化(依赖注入、Flutter)
dynamic 可变 可空 任意时间 动态语言互操作(谨慎)
final 不可变 默认不可空 首次赋值时 运行时常量(单次赋值)
final Type? 不可变 可空 首次赋值时 可能为 null 的单次赋值
const 不可变 不可空 编译时 编译时常量(字面量、数学常量)

未赋值的变量或者常量,默认是null

内置类型

类型 字面量示例 说明
int 42, 0x2A, 0b101010 整数(64位)
double 3.14, 1.2e5, double.nan 双精度浮点数
num 上述两者父类 抽象数字类型
String 'hello', "world", '''多行''' UTF-16 字符串
bool true, false 布尔值
List<E> [1,2,3], [...list] 有序列表
Set<E> {1,2,3} 无序集合
Map<K,V> {'a':1, 'b':2} 键值对
Runes 通过字符串的 .runes 获得 Unicode 码点序列
Symbol #foo 标识符符号
Record (42, 'ans'), (x:1, y:2) 匿名聚合类型
Future<T> 异步操作结果 见异步章节
Stream<T> 异步事件序列 见异步章节
Function 函数对象 函数类型
Never - 永不返回的类型
void - 无返回值

1、 num / int / double 数字操作

ini 复制代码
// int 和 double 共享 num 的方法
int i = -42;
double d = 3.14;

// 属性
bool isNeg = i.isNegative;               // true
bool isFinite = d.isFinite;              // true
bool isInfinite = d.isInfinite;          // false
bool isNaN = d.isNaN;                    // false

// 转换
int abs = i.abs();                       // 42
double d2 = i.toDouble();                // -42.0
int i2 = d.toInt();                      // 3
int round = d.round();                   // 3
int floor = d.floor();                   // 3
int ceil = d.ceil();                     // 4
int trunc = d.truncate();                // 3

// 字符串转换
String s1 = i.toString();                // "-42"
String s2 = d.toStringAsFixed(1);        // "3.1"
String s3 = d.toStringAsPrecision(2);    // "3.1"
String hex = 255.toRadixString(16);      // "ff"
int parsed = int.parse('ff', radix: 16); // 255

// 比较
int cmp = i.compareTo(10);               // -1 (小于)
bool isEven = i.isEven;                  // true (偶数)
bool isOdd = i.isOdd;                    // false

// 位运算(仅 int)
int mask = 0b1010;
int shifted = mask << 2;                 // 0b101000
int complemented = ~mask;                // 按位非
int and = mask & 0b1100;                 // 0b1000
int or = mask | 0b0101;                  // 0b1111
int xor = mask ^ 0b1111;                 // 0b0101

2、String 字符串操作

ini 复制代码
// 创建字符串
String s1 = 'Hello';
String s2 = "World";
String s3 = '''多行
字符串''';
String s4 = r'原始字符串\n不转义';

// 属性
int len = s1.length;                  // 长度: 5
bool isEmpty = s1.isEmpty;            // 是否为空: false
bool isNotEmpty = s1.isNotEmpty;      // 是否非空: true

// 访问字符
String firstChar = s1[0];             // 'H'
int codeUnit = s1.codeUnitAt(0);      // 72 (UTF-16 码元)
List<int> codeUnits = s1.codeUnits;   // [72, 101, 108, 108, 111]
Runes runes = s1.runes;               // Unicode 码点可迭代

// 大小写转换
String upper = s1.toUpperCase();      // "HELLO"
String lower = s1.toLowerCase();      // "hello"

// 修剪
String padded = '  abc  ';
String trimmed = padded.trim();       // "abc"
String leftTrim = padded.trimLeft();  // "abc  "
String rightTrim = padded.trimRight();// "  abc"

// 填充
String padLeft = '5'.padLeft(3, '0'); // "005"
String padRight = '5'.padRight(3, '0');// "500"

// 比较与包含
bool eq = s1 == 'Hello';              // true
int compare = s1.compareTo('Hello');  // 0
bool contains = s1.contains('ell');   // true
bool starts = s1.startsWith('He');    // true
bool ends = s1.endsWith('lo');        // true

// 查找位置
int idx = s1.indexOf('l');            // 2
int lastIdx = s1.lastIndexOf('l');    // 3

// 截取
String sub = s1.substring(1, 4);      // "ell" (索引1~3)
String subToEnd = s1.substring(2);    // "llo"

// 替换
String replaced = s1.replaceAll('l', 'x');     // "Hexxo"
String firstReplaced = s1.replaceFirst('l', 'x'); // "Hexlo"
String rangeReplaced = s1.replaceRange(1, 3, 'yy'); // "Hyylo"

// 分割与连接
List<String> parts = 'a,b,c'.split(',');        // ['a','b','c']
String joined = ['a','b'].join('-');            // "a-b"

// 正则相关
RegExp reg = RegExp(r'\d+');
bool hasMatch = reg.hasMatch('abc123');         // true
String replacedAll = 'a1b2c3'.replaceAll(reg, '#'); // "a#b#c#"
Iterable<Match> matches = reg.allMatches('a1b2c3');

// 其他转换
int? intVal = int.tryParse('123');              // 123
double? doubleVal = double.tryParse('3.14');    // 3.14

3、 List<E> -- 有序可重复列表

3.1、工厂构造方法(创建 List)

ini 复制代码
// 工厂构造方法
List<int> list1 = List.filled(3, 0);              // 创建固定长度列表 [0, 0, 0]
List<int> list2 = List.generate(3, (i) => i);     // 通过生成器创建 [0, 1, 2]
List<int> list3 = List.empty();                   // 创建空列表 []
List<int> list4 = List.from([1, 2, 3]);           // 从 Iterable 创建 [1, 2, 3]
List<int> list5 = List.of([1, 2, 3]);             // 从 Iterable<E> 创建 [1, 2, 3]
List<int> list6 = List.unmodifiable([1, 2, 3]);   // 创建不可修改列表 [1, 2, 3]
List<int> list7 = [1, 2, 3];                      // 字面量创建 [1, 2, 3]

3.2、实例属性(读取)

ini 复制代码
List<int> list = [1, 2, 3, 2];
int len = list.length;                // 获取长度: 4
int first = list.first;               // 第一个元素: 1
int last = list.last;                 // 最后一个元素: 2
bool empty = list.isEmpty;            // 是否为空: false
bool notEmpty = list.isNotEmpty;      // 是否非空: true
Iterable<int> rev = list.reversed;    // 逆序迭代器: (2, 3, 2, 1)
int single = list.single;             // 仅当列表只有一个元素时使用,否则抛异常
Iterator<int> it = list.iterator;     // 获取迭代器
int hashCode = list.hashCode;         // 对象的哈希码
Type type = list.runtimeType;         // 运行时类型: List<int>

3.3、操作符
ini 复制代码
List<int> list = [1, 2, 3];
int value = list[0];                  // 访问索引 0: 1
list[1] = 10;                         // 修改索引 1: [1, 10, 3]
List<int> combined = list + [4, 5];   // 连接列表: [1, 10, 3, 4, 5]
bool equal = list == [1, 2, 3];       // 判断相等: false

3.4、添加 / 插入 / 扩展元素
ini 复制代码
List<int> list = [1, 2, 3];
list.add(4);                          // 末尾添加: [1, 2, 3, 4]
list.addAll([5, 6]);                  // 添加多个: [1, 2, 3, 4, 5, 6]
list.insert(0, 0);                    // 插入到索引 0: [0, 1, 2, 3, 4, 5, 6]
list.insertAll(2, [99, 98]);          // 从索引2插入多个: [0, 1, 99, 98, 2, 3, 4, 5, 6]
list.setAll(1, [10, 20]);             // 从索引1开始覆盖: [0, 10, 20, 98, 2, 3, 4, 5, 6]

3.5、删除 / 移除元素
scss 复制代码
List<int> list = [1, 2, 3, 2, 4];
list.remove(2);                       // 删除第一个 2: [1, 3, 2, 4]
list.removeAt(2);                     // 删除索引 2 的元素: [1, 3, 4]
list.removeLast();                    // 删除最后一个: [1, 3]
list.removeWhere((n) => n.isOdd);     // 删除所有奇数: [3]? 实际测试:移除奇数后剩下 [3]? 初始 [1,3,4] 移除奇数 1,3 → [4]
// 重新初始化
list = [1, 2, 3, 4, 5];
list.removeRange(1, 3);               // 删除索引 1~2: [1, 4, 5]
list.retainWhere((n) => n.isEven);    // 只保留偶数: [4]
list.clear();                         // 清空所有元素: []

3.6、修改 / 替换元素(不改变长度)

dart

ini 复制代码
List<int?> list = [1, 2, 3, 4, 5];
list.fillRange(1, 3, 0);              // 将索引 1~2 填充为 0: [1, 0, 0, 4, 5]
list.replaceRange(0, 2, [99, 98]);    // 替换 [0,2) 为 [99,98]: [99, 98, 0, 4, 5]
list.setRange(1, 3, [10, 20]);        // 从索引1开始复制 [10,20] 到 [1,3): [99, 10, 20, 4, 5]
list.setAll(2, [30, 40]);             // 从索引2覆盖两个元素: [99, 10, 30, 40, 5] (注意长度不变)

3.7、查询 / 检查元素
ini 复制代码
List<int> list = [1, 2, 3, 2, 4];
int idx = list.indexOf(2);            // 第一个 2 的索引: 1
int lastIdx = list.lastIndexOf(2);    // 最后一个 2 的索引: 3
bool has = list.contains(3);          // 是否包含 3: true
bool anyEven = list.any((n) => n.isEven);   // 是否有偶数: true
bool allPositive = list.every((n) => n > 0); // 是否都为正: true

3.8、获取子列表 / 部分
ini 复制代码
List<int> list = [1, 2, 3, 4, 5];
List<int> sub = list.sublist(1, 4);   // 索引 1~3: [2, 3, 4]
Iterable<int> range = list.getRange(1, 3); // 惰性视图索引 1~2: (2, 3)
Iterable<int> taken = list.take(2);   // 前两个: (1, 2)
Iterable<int> takeWhile = list.takeWhile((n) => n < 4); // 小于4的前缀: (1, 2, 3)
Iterable<int> skipped = list.skip(2); // 跳过前两个: (3, 4, 5)
Iterable<int> skipWhile = list.skipWhile((n) => n < 3); // 跳过小于3的: (3, 4, 5)

3.9、转换(产生新列表或其他类型)
ini 复制代码
List<int> list = [1, 2, 2, 3];
List<int> copy = list.toList();               // 复制新列表: [1,2,2,3]
Set<int> set = list.toSet();                  // 转为 Set: {1,2,3}
Map<int, int> map = list.asMap();             // 转为 Map: {0:1, 1:2, 2:2, 3:3}
List<num> casted = list.cast<num>();          // 类型转换视图: [1,2,2,3] 作为 List<num>
Iterable<int> mapped = list.map((n) => n * 2); // 每个元素乘2: (2,4,4,6)
Iterable<int> expanded = list.expand((n) => [n, n]); // 每个元素展开两次: (1,1,2,2,2,2,3,3)
Iterable<int> filtered = list.where((n) => n.isEven); // 筛选偶数: (2,2)
Iterable<int> typed = list.whereType<int>();   // 筛选指定类型: (1,2,2,3)
Iterable<int> reversedView = list.reversed;    // 逆序视图: (3,2,2,1)
Iterable<int> followed = list.followedBy([4,5]); // 连接后续: (1,2,2,3,4,5)

3.10、排序 / 随机化
scss 复制代码
List<int> list = [3, 1, 4, 2];
list.sort();                             // 升序排序: [1, 2, 3, 4]
list.sort((a, b) => b.compareTo(a));     // 降序排序: [4, 3, 2, 1]
list.shuffle();                          // 随机打乱顺序,例如 [2, 4, 1, 3]

3.11、归约 / 聚合操作
ini 复制代码
List<int> list = [1, 2, 3, 4];
int sum = list.reduce((a, b) => a + b);  // 累加: 10
int foldSum = list.fold(10, (p, e) => p + e); // 带初始值累加: 20
String joined = list.join('-');          // 用 '-' 连接: "1-2-3-4"

3.12、迭代 / 遍历
scss 复制代码
List<int> list = [1, 2, 3];
list.forEach((e) => print(e));           // 依次打印 1, 2, 3
Iterator<int> it = list.iterator;        // 获取迭代器
while (it.moveNext()) print(it.current); // 手动迭代

3.13 继承自 Iterable 的其他常用方法
ini 复制代码
List<int> list = [1, 2, 3, 2];
int elem = list.elementAt(2);            // 索引 2 的元素: 3
int firstEven = list.firstWhere((n) => n.isEven); // 第一个偶数: 2
int lastOdd = list.lastWhere((n) => n.isOdd);     // 最后一个奇数: 3
int singleEven = list.singleWhere((n) => n > 5, orElse: () => -1); // 无满足条件返回 -1

3.14 修改长度的操作(影响 length 属性)
ini 复制代码
List<int?> list = [1, 2, 3];
list.length = 5;                         // 扩展长度,新元素为 null: [1, 2, 3, null, null]
list.length = 2;                         // 截断长度: [1, 2]

3.15 注意:不可变列表的行为
ini 复制代码
List<int> unmod = List.unmodifiable([1, 2, 3]);
// unmod.add(4);                         // 抛出 UnsupportedError
// unmod[0] = 10;                        // 抛出 UnsupportedError

4、Set<E> -- 无序不可重复

ini 复制代码
Set<int> set = {1, 2, 3};

// 属性
int len = set.length;                    // 3
bool isEmpty = set.isEmpty;              // false
bool isNotEmpty = set.isNotEmpty;        // true

// 添加/删除
set.add(4);                              // {1,2,3,4}
set.addAll([5,6]);                       // {1,2,3,4,5,6}
set.remove(3);                           // 删除成功返回 true
set.removeWhere((e) => e.isEven);        // 删除所有偶数: {1,5}
set.retainWhere((e) => e > 2);           // 只保留大于2的: {5}
set.clear();                             // {}

// 集合运算
Set<int> a = {1,2,3};
Set<int> b = {2,3,4};
Set<int> union = a.union(b);             // {1,2,3,4}
Set<int> inter = a.intersection(b);      // {2,3}
Set<int> diff = a.difference(b);         // {1}
Set<int> symDiff = a.union(b).difference(a.intersection(b)); // {1,4}

// 包含
bool contains = a.contains(2);           // true
bool containsAll = a.containsAll([1,2]); // true

// 遍历
a.forEach(print);
Iterable<int> mapped = a.map((e) => e*2);

// 转换
List<int> list = a.toList();             // [1,2,3]
Set<int> copy = Set.from(a);             // 拷贝
Set<int> of = Set.of(a);                 // 类型安全拷贝

5、Map<K, V> -- 键值对

dart 复制代码
Map<String, int> map = {'a': 1, 'b': 2};

// 属性
int len = map.length;                    // 2
bool isEmpty = map.isEmpty;              // false
bool isNotEmpty = map.isNotEmpty;        // true
Iterable<String> keys = map.keys;        // ('a', 'b')
Iterable<int> values = map.values;       // (1, 2)
Iterable<MapEntry<String, int>> entries = map.entries;

// 访问
int? val = map['a'];                     // 1
int? missing = map['c'];                 // null
int valueOrDefault = map['c'] ?? 0;      // 0

// 添加/修改
map['c'] = 3;                            // 添加或更新
map.addAll({'d': 4, 'e': 5});            // 批量添加
map.putIfAbsent('f', () => 6);           // 如果不存在则添加

// 删除
map.remove('a');                         // 移除键 'a',返回被删值 1
map.removeWhere((key, value) => value.isEven); // 删除所有值为偶数的项

// 更新
map.update('b', (value) => value + 10);  // 更新已有键
map.update('missing', (v) => 0, ifAbsent: () => 99); // 不存在则添加 99

// 查询
bool containsKey = map.containsKey('b'); // true
bool containsValue = map.containsValue(2); // true

// 遍历
map.forEach((key, value) => print('$key: $value'));

// 转换
Map<String, int> copy = Map.from(map);    // 浅拷贝
Map<String, int> of = Map.of(map);        // 类型安全拷贝
Map<int, String> reversed = map.map((key, value) => MapEntry(value, key));
// 清空
map.clear();                              // {}

5、DateTime

ini 复制代码
// 创建
DateTime now = DateTime.now();
DateTime specific = DateTime(2025, 3, 15, 14, 30, 45);
DateTime utc = DateTime.utc(2025, 3, 15);
DateTime parse = DateTime.parse('2025-03-15T14:30:00');

// 属性
int year = now.year;
int month = now.month;
int day = now.day;
int hour = now.hour;
int minute = now.minute;
int second = now.second;
int millisecond = now.millisecond;
int microsecond = now.microsecond;
int weekday = now.weekday;               // 1=Mon, 7=Sun
bool isUtc = now.isUtc;

// 比较
bool isAfter = now.isAfter(specific);    // true/false
bool isBefore = now.isBefore(specific);
int diffDays = now.difference(specific).inDays;

// 添加时间
DateTime later = now.add(Duration(days: 5, hours: 3));
DateTime earlier = now.subtract(Duration(minutes: 30));

// 转换
String iso = now.toIso8601String();      // "2025-03-15T14:30:45.123Z"
String localStr = now.toString();
DateTime toLocal = utc.toLocal();
DateTime toUtc = now.toUtc();

// 比较两个 DateTime
int compareResult = now.compareTo(specific);

6、Runes 是 Unicode 码点的序列,用于处理超出 UTF-16 的字符(如 emoji)。

ini 复制代码
// 字面量中的 Unicode
String emoji = '🌟';               // 单个码点 U+1F31F
String heart = '\u2665';           // ♥
String smile = '\u{1F600}';        // 😀

// 获取 Runes
Runes runes = emoji.runes;         // [127775]

// 遍历
for (int codePoint in runes) {
  print(String.fromCharCode(codePoint));
}

7、Symbol 表示 Dart 程序中的标识符,主要用于反射(尽管反射在 Flutter 中不可用)或与 JS 互操作。

ini 复制代码
Symbol sym = #foo;        // 字面量
Symbol fromStr = Symbol('foo');
print(sym == fromStr);  

8、FutureStream 类型(异步相关)

虽然它们也属于数据类型,但主要涉及异步编程,这里简要提及。

dart 复制代码
Future

// 创建 Future
Future<String> f1 = Future.value('done');
Future<String> f2 = Future.error('error');
Future<String> f3 = Future.delayed(Duration(seconds: 1), () => 'result');

// 注册回调
f3.then((value) => print(value));
f3.catchError((err) => print(err));
f3.whenComplete(() => print('完成'));

// 静态方法
Future<void> wait = Future.wait([f1, f3]); // 等待多个 Future
Future<String> any = Future.any([f1, f3]); // 任何一个完成
Future<String> sync = Future.sync(() => 'sync');

// async/await 配合
void main() async {
  String res = await f3;
}

Stream

// 创建 Stream
Stream<int> stream = Stream.fromIterable([1,2,3]);
Stream<int> periodic = Stream.periodic(Duration(seconds: 1), (i) => i);

// 监听
StreamSubscription<int> sub = stream.listen(
  (data) => print(data),
  onError: (err) => print(err),
  onDone: () => print('done'),
);

// 转换
Stream<int> mapped = stream.map((e) => e * 2);
Stream<int> where = stream.where((e) => e.isEven);
Stream<int> expanded = stream.expand((e) => [e, e]);

// 聚合
Future<int> sum = stream.reduce((a, b) => a + b);
Future<int> fold = stream.fold(0, (p, e) => p + e);
Future<List<int>> list = stream.toList();
Future<Set<int>> set = stream.toSet();

// 异步循环
await for (int value in stream) {
  print(value);
}

9、Record 类型(Dart 3 新增)

记录(Record)是 Dart 3 引入的匿名、不可变、聚合类型。

scss 复制代码
// 位置字段
(int, String) pair = (42, 'answer');
print(pair.$1);   // 42
print(pair.$2);   // 'answer'

// 命名字段
({int x, int y}) point = (x: 10, y: 20);
print(point.x);   // 10

// 混合
({int x, int y, int}) triple = (x: 1, y: 2, 3);
print(triple.$1);  // 3(位置字段索引从 1 开始)
print(triple.x);   // 1

// 类型别名
typedef Point = ({int x, int y});
Point p = (x: 0, y: 0);

10、泛型类型(<>

Dart 支持泛型,允许定义类型参数化的类、函数和接口。

csharp 复制代码
// 泛型class
class Box<T> {
  T content;
  Box(this.content);
}
void main() {
  Box<int> intBox = Box<int>(42);
  Box<String> strBox = Box<String>('hello');
  
  // 类型推断
  var autoBox = Box(123);   // Box<int>
}
    


// 泛型函数
T first<T>(List<T> list) => list[0];

11、泛型别名 (typedef)

ini 复制代码
typedef IntList = List<int>;
IntList numbers = [1,2,3];

// 函数类型别名
typedef Adder = int Function(int, int);
Adder add = (a, b) => a + b;

// 泛型 typedef
typedef Transformer<T> = T Function(T);
Transformer<String> upper = (s) => s.toUpperCase();

12、Never 类型

Never 表示永远不会正常返回的表达式(例如抛出异常或无限循环)。

csharp 复制代码
Never alwaysThrow() {
  throw Exception('Always');
}

运算符

scss 复制代码
/// Dart 运算符完整示例
/// 涵盖:算术、关系、逻辑、位运算、赋值、条件、类型测试、空安全、级联、索引等

void main() {
  // =========================================================================
  // 一、算术运算符
  // =========================================================================
  int a = 10, b = 3;
  print(a + b);   // 加法: 13
  print(a - b);   // 减法: 7
  print(a * b);   // 乘法: 30
  print(a / b);   // 除法: 3.3333333333333335 (double)
  print(a ~/ b);  // 整除: 3
  print(a % b);   // 取模: 1
  print(-a);      // 取负: -10

  // 自增自减
  int c = 5;
  print(c++);     // 后置自增: 输出5, c变为6
  print(++c);     // 前置自增: c变为7, 输出7
  print(c--);     // 后置自减: 输出7, c变为6
  print(--c);     // 前置自减: c变为5, 输出5

  // =========================================================================
  // 二、关系运算符(结果为 bool)
  // =========================================================================
  int x = 5, y = 8;
  print(x == y);   // 相等: false
  print(x != y);   // 不等: true
  print(x > y);    // 大于: false
  print(x < y);    // 小于: true
  print(x >= y);   // 大于等于: false
  print(x <= y);   // 小于等于: true

  // =========================================================================
  // 三、逻辑运算符(操作 bool)
  // =========================================================================
  bool p = true, q = false;
  print(!p);       // 逻辑非: false
  print(p && q);   // 逻辑与: false
  print(p || q);   // 逻辑或: true

  // 短路求值示例
  bool expensive() { print('expensive called'); return true; }
  bool result = p || expensive(); // p为true,不会调用expensive()
  print(result);  // true

  // =========================================================================
  // 四、位运算符(仅用于 int)
  // =========================================================================
  int bitA = 0b1010;  // 10
  int bitB = 0b1100;  // 12
  print(bitA & bitB);           // 按位与: 8 (0b1000)
  print(bitA | bitB);           // 按位或: 14 (0b1110)
  print(bitA ^ bitB);           // 按位异或: 6 (0b0110)
  print(~bitA);                 // 按位非: -11 (补码)
  print(bitA << 1);             // 左移: 20 (0b10100)
  print(bitA >> 1);             // 右移: 5 (0b101)
  print(bitA >>> 1);            // 无符号右移 (Dart 2.14+): 5 (0b101)

  // =========================================================================
  // 五、赋值运算符
  // =========================================================================
  int v = 10;
  v += 3;   // v = v + 3 → 13
  v -= 2;   // 11
  v *= 2;   // 22
  v ~/= 3;  // 7 (整除)
  v %= 3;   // 1
  v <<= 1;  // 2
  v >>= 1;  // 1
  v |= 4;   // 5 (0b101)
  v &= 3;   // 1 (0b001)
  v ^= 2;   // 3 (0b011)
  print(v); // 3

  // 空安全赋值 ??=
  int? nullable;
  nullable ??= 42;   // 因为 nullable 为 null,赋值为 42
  nullable ??= 99;   // 已经有值,保持不变
  print(nullable);   // 42

  // =========================================================================
  // 六、条件运算符(三元)
  // =========================================================================
  int score = 85;
  String grade = (score >= 60) ? 'Pass' : 'Fail';
  print(grade);   // Pass

  // =========================================================================
  // 七、类型测试与转换运算符
  // =========================================================================
  dynamic obj = 'Hello';
  print(obj is String);    // true
  print(obj is! int);      // true
  String str = obj as String;  // 类型转换(安全,因为运行时是 String)
  print(str);              // Hello

  // 错误示例(注释掉会抛异常):
  // int wrong = obj as int;   // 抛出 CastError

  // =========================================================================
  // 八、空安全运算符
  // =========================================================================
  // 8.1 ?? 空值合并
  int? aNull;
  int val = aNull ?? 42;    // aNull为null,取42
  print(val);   // 42

  // 8.2 ?. 安全调用
  String? name;
  int? len = name?.length;  // name为null,结果为null,不会抛出异常
  print(len);   // null

  // 8.3 空感知扩展 ...?(集合中使用)
  List<int>? maybeList;
  List<int> combined = [0, ...?maybeList];  // maybeList为null,展开为空
  print(combined);  // [0]

  // 8.4 ?[] 安全索引(Dart 2.17+)
  List<int>? list;
  int? first = list?[0];    // list为null,结果为null
  print(first);   // null

  // =========================================================================
  // 九、级联运算符
  // =========================================================================
  // 9.1 .. 级联,允许连续调用
  var sb = StringBuffer()
    ..write('Hello')
    ..write(' ')
    ..write('World')
    ..write('!');
  print(sb.toString());   // Hello World!

  // 9.2 ?.. 空感知级联(如果对象不为null才执行)
  List<int>? cascadeList;
  cascadeList?..add(1)..add(2);   // cascadeList为null,什么也不做
  print(cascadeList);   // null

  List<int> nonNullList = [];
  nonNullList?..add(1)..add(2);   // 正常执行
  print(nonNullList);   // [1, 2]

  // =========================================================================
  // 十、索引/下标运算符
  // =========================================================================
  List<String> fruits = ['apple', 'banana', 'orange'];
  print(fruits[0]);      // 访问: apple
  fruits[1] = 'blueberry';  // 修改
  print(fruits);         // [apple, blueberry, orange]

  // 可空安全索引(已见 ?[])
  // =========================================================================
  // 十一、其他运算符
  // =========================================================================
  // 成员访问 .
  print(fruits.length);  // 3

  // 函数调用 ()
  void sayHi() => print('Hi');
  sayHi();               // 调用函数

  // 逗号 , 用于分隔参数、列表元素等
  var list = [1, 2, 3];

  // =========================================================================
  // 十二、运算符优先级演示(括号改变优先级)
  // =========================================================================
  int priority = 2 + 3 * 4;        // 乘法优先: 14
  int withParen = (2 + 3) * 4;     // 括号优先: 20
  print('优先级: $priority vs $withParen');
}                                                           

函数

scss 复制代码
/// Dart 函数声明完整示例
/// 包含:命名函数、箭头函数、可选参数、匿名函数、闭包、高阶函数等

void main() {
  // 调用各种函数来演示
  print('=== 基本函数 ===');
  print(add(3, 5));           // 8
  print(multiply(4, 6));      // 24

  print('\n=== 可选参数 ===');
  greet('Alice');             // Hello, Alice! You are 0 years old.
  greet('Bob', age: 25);      // Hello, Bob! You are 25 years old.
  logMessage('Error');        // [INFO] Error
  logMessage('Warning', 'WARN'); // [WARN] Warning

  print('\n=== 返回值类型 ===');
  print(getPi());             // 3.14159
  doNothing();                // (无输出)

  print('\n=== 匿名函数 ===');
  var square = (int x) => x * x;
  print(square(5));           // 25

  print('\n=== 闭包 ===');
  var counter = makeCounter();
  print(counter());           // 0
  print(counter());           // 1

  print('\n=== 函数作为参数 ===');
  applyOperation(3, 4, add);          // 3 + 4 = 7
  applyOperation(3, 4, multiply);     // 3 * 4 = 12

  print('\n=== 函数作为返回值 ===');
  var myAdder = createAdder(10);
  print(myAdder(5));          // 15

  print('\n=== 生成器函数(同步/异步) ===');
  print(syncRange(3).toList());       // [0, 1, 2]
  asyncRange(3).listen((v) => print('async: $v')); // 异步输出 0,1,2

  print('\n=== Never 函数 ===');
  // 下面的调用会抛出异常,演示时注释掉
  // throwError();   // 抛出 Exception: Always throws!
}

// =========================================================================
// 一、基本函数声明
// =========================================================================

/// 普通函数,显式指定参数类型和返回值类型
int add(int a, int b) {
  return a + b;
}

/// 单行表达式函数(箭头函数)
int multiply(int a, int b) => a * b;

// =========================================================================
// 二、可选参数
// =========================================================================

/// 命名参数(使用 {}),可设置默认值
void greet(String name, {int age = 0, String? title}) {
  print('Hello, ${title != null ? '$title ' : ''}$name! You are $age years old.');
}

/// 位置可选参数(使用 []),可设置默认值
void logMessage(String msg, [String prefix = 'INFO']) {
  print('[$prefix] $msg');
}

// =========================================================================
// 三、返回值类型
// =========================================================================

/// 明确返回值类型
double getPi() => 3.14159;

/// void 表示无返回值(可省略 return,或 return; 或 return null;)
void doNothing() {
  // 没有 return 语句
}

/// Never 表示函数永远不会正常返回(总是抛出异常或无限循环)
Never throwError() {
  throw Exception('Always throws!');
}

// =========================================================================
// 四、匿名函数(Lambda)
// =========================================================================

// 匿名函数赋值给变量
var divide = (double a, double b) {
  if (b == 0) throw ArgumentError('Division by zero');
  return a / b;
};

// 匿名函数在集合操作中使用
var numbers = [1, 2, 3];
var doubled = numbers.map((n) => n * 2).toList(); // 箭头形式匿名函数

// =========================================================================
// 五、闭包(捕获外部变量)
// =========================================================================

/// 返回一个闭包,每次调用增加计数
Function makeCounter() {
  int count = 0;
  return () => count++;
}

// =========================================================================
// 六、函数作为一等公民(高阶函数)
// =========================================================================

/// 接收函数作为参数
void applyOperation(int a, int b, int Function(int, int) operation) {
  print('Result: ${operation(a, b)}');
}

/// 返回一个函数
Function createAdder(int addend) {
  return (int value) => value + addend;
}

// =========================================================================
// 七、生成器函数(同步和异步)
// =========================================================================

/// 同步生成器:返回 Iterable,使用 sync* 和 yield
Iterable<int> syncRange(int limit) sync* {
  for (int i = 0; i < limit; i++) {
    yield i;
  }
}

/// 异步生成器:返回 Stream,使用 async* 和 yield
Stream<int> asyncRange(int limit) async* {
  for (int i = 0; i < limit; i++) {
    await Future.delayed(Duration(milliseconds: 100));
    yield i;
  }
}

// =========================================================================
// 八、函数类型别名(typedef)
// =========================================================================

/// 为函数类型定义别名,提高可读性
typedef IntOperation = int Function(int, int);

/// 使用 typedef
int executeOperation(IntOperation op, int x, int y) => op(x, y);

// 演示 typedef
void demoTypeDef() {
  IntOperation addOp = (a, b) => a + b;
  print(executeOperation(addOp, 10, 20)); // 30
}

// =========================================================================
// 九、静态函数与实例方法(在类中)
// =========================================================================

class Calculator {
  // 实例方法
  int addInstance(int a, int b) => a + b;

  // 静态方法(类方法)
  static int multiplyStatic(int a, int b) => a * b;
}

// 调用示例(可以在 main 中取消注释)
void demoClassMethods() {
  var calc = Calculator();
  print(calc.addInstance(2, 3));        // 5
  print(Calculator.multiplyStatic(2, 3)); // 6
}

控制流程

控制流类型 关键字/语法 说明
条件分支 ifelse ifelse 布尔条件判断
多值匹配(传统) switchcasebreakdefault 需要显式 break 避免穿透
多值匹配(Dart 3 表达式) switch (expr) { pattern => value } 可作为值返回,无需 break
模式匹配 if-casefor-in 模式、case ... when 解构、类型匹配、守卫
循环 forfor-inwhiledo-while 标准循环与迭代
跳转 breakcontinuereturn、标签 控制循环退出或跳过
断言 assert(condition, message) 开发期检查,生产环境忽略

异常处理

特性 语法/示例 说明
抛出异常 throw Exception('msg') 可抛出任何非 null 对象,通常抛出 ExceptionError 子类
基本捕获 try { ... } catch (e) { ... } 捕获任意异常,e 是异常对象
指定类型捕获 on FormatException catch (e) 只捕获特定类型异常
获取堆栈 catch (e, stackTrace) 第二个参数是 StackTrace 对象
finally finally { ... } 无论是否发生异常都会执行
重新抛出 rethrow catch 块内重新抛出当前异常,保留原始堆栈
自定义异常 class MyEx implements Exception 通常实现 Exception 接口,可覆盖 toString()
异步捕获 try { await future } catch (e) async 函数中使用 try-catch 捕获 Future 异常
Future 捕获 future.catchError((e) => ...) 链式调用处理错误
Stream 错误 listen(..., onError: (e) => ...) 监听时注册错误回调
await for 捕获 try { await for (x in stream) } catch (e) try 包裹整个循环

面向对象基础与进阶(类和构造函数)

一、类的基本定义

1.1 最简单的类
csharp 复制代码
class Person {
  // 实例变量(成员变量)
  String name;
  int age;

  // 构造函数(语法糖:自动将参数赋值给 this 对应的变量)
  Person(this.name, this.age);

  // 实例方法
  void sayHello() {
    print('Hello, I am $name, $age years old.');
  }
}

void main() {
  var p = Person('Alice', 30);
  p.sayHello();  // 输出:Hello, I am Alice, 30 years old.
}
1.2 带构造函数体的类
arduino 复制代码
class Rectangle {
  double width, height;

  // 构造函数体中可以添加逻辑
  Rectangle(double w, double h) {
    width = w;
    height = h;
    print('创建矩形: $width x $height');
  }
}

void main() {
  var rect = Rectangle(5.0, 3.0); // 输出:创建矩形: 5.0 x 3.0
}

二、构造函数详解

2.1 默认构造函数

如果没有显式声明构造函数,Dart 会提供一个无参的默认构造函数(仅调用父类的无参构造函数)。

scala 复制代码
class Animal {
  Animal() {
    print('Animal 构造函数被调用');
  }
}

class Dog extends Animal {
  // 没有显式构造函数,默认有一个 Dog() : super()
}

void main() {
  var dog = Dog(); // 输出:Animal 构造函数被调用
}
2.2 普通构造函数(支持重载?不行,通过命名构造函数实现)

Dart 不支持多个同名构造函数(函数重载),但支持命名构造函数

dart 复制代码
class Point {
  double x, y;

  // 普通构造函数
  Point(this.x, this.y);

  // 命名构造函数
  Point.origin() : x = 0, y = 0;

  // 命名构造函数
  Point.fromJson(Map<String, double> json) : x = json['x']!, y = json['y']!;
}

void main() {
  var p1 = Point(3, 4);
  var p2 = Point.origin();
  var p3 = Point.fromJson({'x': 1.5, 'y': 2.5});
  print('${p1.x},${p1.y}'); // 3,4
  print('${p2.x},${p2.y}'); // 0,0
  print('${p3.x},${p3.y}'); // 1.5,2.5
}
2.3 重定向构造函数

一个构造函数可以委托给同一类的另一个构造函数(使用 : this(...))。

dart 复制代码
class Vector {
  double x, y;

  Vector(this.x, this.y);

  // 重定向到主构造函数 (twoD 这个是方法名称)
  Vector.twoD(double x, double y) : this(x, y);

  // 重定向到命名构造函数
  Vector.zero() : this(0, 0);
}

void main() {
  var v = Vector.twoD(2, 3);
  print('${v.x},${v.y}'); // 2,3
}
2.4 常量构造函数

使用 const 关键字创建编译时常量对象。要求所有实例变量必须是 final

arduino 复制代码
class ImmutablePoint {
  final int x, y;

  const ImmutablePoint(this.x, this.y);
}

void main() {
  const p1 = ImmutablePoint(1, 2);
  const p2 = ImmutablePoint(1, 2);
  print(identical(p1, p2)); // true(同一个常量实例)
}
2.5 工厂构造函数(factory

工厂构造函数不总是创建新实例,可以从缓存中返回现有实例,或返回子类实例。工厂构造函数无法访问 this

dart 复制代码
class Logger {
  final String name;
  static final Map<String, Logger> _cache = {};

  // 工厂构造函数:返回缓存中的实例或新实例
  factory Logger(String name) {
    return _cache.putIfAbsent(name, () => Logger._internal(name));
  }

  // 私有构造函数
  Logger._internal(this.name);
}

void main() {
  var logger1 = Logger('UI');
  var logger2 = Logger('UI');
  print(identical(logger1, logger2)); // true(同一个实例)
}

三、初始化列表与 final 变量

3.1 初始化列表

在构造函数体执行前初始化实例变量,常用于初始化 final 变量或进行断言。

scss 复制代码
class Circle {
  final double radius;
  static const double pi = 3.14159;

  // 初始化列表(: 后跟表达式)
  Circle(double r) : radius = r, assert(r > 0);

  // 也可以调用父类构造函数
  Circle.fromDiameter(double diameter) : radius = diameter / 2;

  double get area => pi * radius * radius;
}

void main() {
  var c = Circle(5.0);
  print(c.area); // 78.53975
}
3.2 使用 late 延迟初始化
javascript 复制代码
class Database {
  late String _connectionString;

  void init(String conn) {
    _connectionString = conn;
  }

  void query() {
    print('Querying $_connectionString');
  }
}

四、成员变量

4.1 实例变量与 getter/setter

每个实例变量隐式包含一个 getter 和 setter(final 变量只有 getter)。也可以自定义 getter/setter。

csharp 复制代码
class Temperature {
  double _celsius; // 私有变量(以下划线开头)

  Temperature(this._celsius);

  // 自定义 getter
  double get celsius => _celsius;

  set celsius(double value) {
    _celsius = value;
  }

  // 计算属性(只有 getter)
  double get fahrenheit => _celsius * 9 / 5 + 32;

  set fahrenheit(double value) {
    _celsius = (value - 32) * 5 / 9;
  }
}

void main() {
  var temp = Temperature(25);
  print(temp.fahrenheit); // 77.0
  temp.fahrenheit = 100;
  print(temp.celsius);    // 37.77777777777778
}
4.2 静态变量与静态方法

静态变量属于类而非实例,在首次访问时初始化。

csharp 复制代码
class AppConfig {
  static const String appName = 'MyApp';
  static int _counter = 0;

  static int get counter => _counter;

  static void increment() {
    _counter++;
  }
}

void main() {
  print(AppConfig.appName); // MyApp
  AppConfig.increment();
  print(AppConfig.counter); // 1
}
4.3 私有成员(库可见性)

使用下划线 _ 前缀声明的成员仅在当前库(文件)内可见。

javascript 复制代码
// 在 my_class.dart 文件中
class _InternalHelper {
  void _hiddenMethod() {}
}

class PublicClass {
  String _privateField = 'secret';

  void _internalLogic() {}
}

五、方法

5.1 实例方法、静态方法、操作符重载
scss 复制代码
class Vector2 {
  final double x, y;

  Vector2(this.x, this.y);

  // 实例方法
  double dot(Vector2 other) => x * other.x + y * other.y;

  // 静态方法
  static Vector2 zero() => Vector2(0, 0);

  // 操作符重载
  Vector2 operator +(Vector2 other) => Vector2(x + other.x, y + other.y);

  @override
  String toString() => '($x, $y)';
}

void main() {
  var v1 = Vector2(1, 2);
  var v2 = Vector2(3, 4);
  print(v1 + v2);      // (4.0, 6.0)
  print(v1.dot(v2));   // 11
  print(Vector2.zero());// (0.0, 0.0)
}

六、继承

6.1 extends@override
scss 复制代码
class Animal {
  void eat() => print('Animal eats');

  Animal() {
    print('Animal constructor');
  }
}

class Dog extends Animal {
  @override
  void eat() {
    super.eat();      // 调用父类方法
    print('Dog eats bone');
  }

  Dog() : super() {
    print('Dog constructor');
  }
}

void main() {
  var dog = Dog();
  dog.eat();
}
// 输出:
// Animal constructor
// Dog constructor
// Animal eats
// Dog eats bone
6.2 继承中的构造函数顺序

子类构造函数体执行前必须调用父类构造函数(默认调用无参构造,或通过 : super(...) 显式调用)。

scala 复制代码
class Parent {
  String name;
  Parent(this.name);
}

class Child extends Parent {
  int age;
  Child(String name, this.age) : super(name); // 必须显式调用父类构造函数
}

七、抽象类与接口

7.1 抽象类(abstract

抽象类不能实例化,可以包含抽象方法(无方法体)。

dart 复制代码
abstract class Shape {
  double get area; // 抽象 getter
  void draw();     // 抽象方法
}

class Circle extends Shape {
  double radius;
  Circle(this.radius);

  @override
  double get area => 3.14159 * radius * radius;

  @override
  void draw() {
    print('Drawing a circle');
  }
}
7.2 隐式接口(implements

每个类都隐式定义了一个接口,其他类可以通过 implements 实现该接口(必须提供所有成员实现)。

dart 复制代码
class Printable {
  void printData() => print('Printing...');
}

class Document implements Printable {
  @override
  void printData() {
    print('Document printing');
  }
}

八、混入(Mixin)

8.1 基本混入(mixin + with

混入用于复用多个类的代码。

scss 复制代码
mixin Flyable {
  void fly() => print('Flying');
}

mixin Swimmable {
  void swim() => print('Swimming');
}

class Duck with Flyable, Swimmable {
  void quack() => print('Quack');
}

void main() {
  var duck = Duck();
  duck.fly();   // Flying
  duck.swim();  // Swimming
  duck.quack(); // Quack
}
8.2 限制混入的使用范围(on 关键字)

mixin 可以指定只能被特定类的子类使用。

scala 复制代码
class Animal {}

mixin Walker on Animal {
  void walk() => print('Walking');
}

class Dog extends Animal with Walker {} // 正确
// class Table with Walker {} // 错误:Table 不是 Animal 的子类
8.3 mixin class(Dart 3)

Dart 3 允许将 mixin 声明为 mixin class,既可以作为普通类使用,也可以作为混入使用。

scala 复制代码
mixin class Serializable {
  void serialize() => print('Serializing');
}

class MyClass extends Serializable {} // 作为普通类继承
class Other with Serializable {}      // 作为混入使用

九、Dart 3 类修饰符

Dart 3 引入了新的类修饰符,用于限制类的使用方式。

修饰符 含义
sealed 禁止在库外继承或实现,用于详尽 switch
base 禁止在库外继承或实现
final 禁止在库外继承或实现,但允许扩展(仅本库)
interface 禁止在库外实现,但可以继承
mixin 只能作为混入使用,不能实例化
mixin class 既可以作类也可以作混入
scala 复制代码
// sealed 示例:详尽 switch
sealed class Result {}

class Success extends Result {
  final int value;
  Success(this.value);
}

class Failure extends Result {
  final String error;
  Failure(this.error);
}

String describe(Result result) => switch (result) {
  Success(value: var v) => 'Success with $v',
  Failure(error: var e) => 'Failure: $e',
}; // 不需要 default,因为 sealed 确保所有子类都已处理

十、 extension 类扩展

扩展方法(Extension Methods)是 Dart 2.7 引入的特性,它允许你在不修改原始类不继承该类 的情况下,为现有类型添加新的方法、getter、setter 或操作符。这对于为第三方库或内置类型(如 StringList)增加便捷功能非常有用

语法:

dart 复制代码
extension <扩展名> on <类型> {
  // 新增的方法、getter、setter、操作符
}
扩展中可定义的成员

扩展中可以定义:

  • 实例方法
  • getter / setter
  • 操作符(如 +-[]
  • 静态方法(不能直接定义,但可以通过类名调用?注意:扩展中不能定义 static 成员,但可以定义普通方法,这些方法仍然需要通过实例调用)
dart 复制代码
extension NumberParsing on String {
  // 方法
  int toInt() => int.parse(this);

  // getter
  bool get isNumeric => double.tryParse(this) != null;

  // setter(一般用于可修改的上下文,但字符串不可变,这里仅演示语法)
  set asInt(int value) => throw UnsupportedError('Strings are immutable');
}

void main() {
  print('123'.toInt());      // 123
  print('abc'.isNumeric);    // false
}
extension 总结
要点 说明
目的 为现有类型添加新成员,无需修改源码或继承
语法 extension Name on Type { ... }
可定义内容 方法、getter、setter、操作符
不可定义 实例变量、静态成员、构造函数
优先级 实例成员 > 扩展成员
解析时机 编译时静态解析(基于声明类型)
冲突处理 使用 show/hide/as 或扩展名限定
典型用例 为内置类型或第三方库添加便捷方法

十一、call

任何类只要定义了 call 方法,它的实例就可以像函数那样被调用(使用 对象() 语法)。call 方法可以定义参数和返回值,调用时就会执行 call 方法体。

11.1 基本示例
javascript 复制代码
class Greeter {
  String call(String name) {
    return 'Hello, $name!';
  }
}

void main() {
  var greeter = Greeter();
  
  // 像函数一样调用实例
  print(greeter('Alice')); // Hello, Alice!
}

十二、类总结与最佳实践

概念 关键字/语法 要点
类定义 class Name { ... } 实例变量、方法
构造函数 ClassName(...) 命名、重定向、常量、工厂
初始化列表 : field = value 在构造函数体前执行
final 变量 final type name 只能赋值一次
静态成员 static 属于类,类名访问
私有成员 _name 库内可见
getter/setter get name / set name 自定义属性访问
继承 extends 单继承,调用 super
抽象类 abstract class 不能实例化
接口实现 implements 多个接口
混入 mixin + with 代码复用
类修饰符 sealed, base, final, interface Dart 3 控制继承/实现

异步编程

一、Future 基础

Future 表示一个延迟计算的结果,它可能是未完成(pending)、成功(completed with value)或失败(completed with error)。

1.1 创建 Future
ini 复制代码
import 'dart:async';

void main() {
  // 1. 直接返回值的 Future
  Future<String> future1 = Future.value('Hello');

  // 2. 立即出错的 Future
  Future<void> future2 = Future.error(Exception('出错了'));

  // 3. 延迟完成的 Future(常用)
  Future<String> future3 = Future.delayed(Duration(seconds: 2), () {
    return '2秒后的结果';
  });

  // 4. 同步执行的 Future(立即执行,但仍为 Future)
  Future<String> future4 = Future.sync(() => '同步计算');

  // 5. 使用 Completer 手动控制(进阶)
  Completer<int> completer = Completer();
  Future<int> future5 = completer.future;
  completer.complete(42); // 手动完成
}
1.2 使用 thencatchError
dart 复制代码
void main() {
  Future.delayed(Duration(seconds: 1), () => '数据')
      .then((value) {
        print('成功: $value');
        return '处理后的数据';
      })
      .then((processed) => print('最终: $processed'))
      .catchError((error) {
        print('捕获错误: $error');
      })
      .whenComplete(() {
        print('无论成功或失败都执行');
      });
}
// 输出:
// 成功: 数据
// 最终: 处理后的数据
// 无论成功或失败都执行
1.3 async / await(推荐)
dart 复制代码
Future<void> fetchData() async {
  try {
    print('开始获取数据');
    String data = await Future.delayed(Duration(seconds: 2), () => '网络数据');
    print('数据: $data');
  } catch (e) {
    print('出错: $e');
  } finally {
    print('清理资源');
  }
}

void main() async {
  await fetchData();
  print('完成');
}
// 输出:
// 开始获取数据
// 数据: 网络数据
// 清理资源
// 完成

二、Stream 基础

Stream 是一系列异步事件的序列,可以随时间接收多个值(类似 Rx 中的 Observable)。

2.1 创建 Stream
ini 复制代码
import 'dart:async';

void main() {
  // 1. 从 Iterable 创建
  Stream<int> stream1 = Stream.fromIterable([1, 2, 3]);

  // 2. 周期性事件
  Stream<int> stream2 = Stream.periodic(Duration(seconds: 1), (count) => count).take(5);

  // 3. 单次事件
  Stream<int> stream3 = Stream.value(42);

  // 4. 错误流
  Stream<int> stream4 = Stream.error(Exception('出错'));

  // 5. 空流
  Stream<int> stream5 = Stream.empty();

  // 6. 使用 StreamController 手动控制(进阶)
  StreamController<int> controller = StreamController();
  Stream<int> stream6 = controller.stream;
  controller.add(1);
  controller.add(2);
  controller.close();
}
2.2 监听 Stream
dart 复制代码
void main() {
  Stream<int> numberStream = Stream.periodic(Duration(milliseconds: 500), (i) => i).take(5);

  // 方式1:listen(常用)
  StreamSubscription<int> subscription = numberStream.listen(
    (value) => print('收到: $value'),        // 数据回调
    onError: (error) => print('错误: $error'),
    onDone: () => print('流结束'),
    cancelOnError: false,                    // 出错后是否自动取消
  );

  // 稍后可以取消订阅
  // subscription.cancel();

  // 方式2:await for(推荐,需在 async 函数中)
  // 见下文
}
// 输出(每0.5秒):
// 收到: 0
// 收到: 1
// 收到: 2
// 收到: 3
// 收到: 4
// 流结束
2.3 使用 await for
dart 复制代码
Future<void> readStream() async {
  Stream<int> stream = Stream.periodic(Duration(milliseconds: 500), (i) => i).take(3);
  await for (int value in stream) {
    print('await for: $value');
  }
  print('循环结束');
}

void main() async {
  await readStream();
}
// 输出:
// await for: 0
// await for: 1
// await for: 2
// 循环结束

三、生成器(Generator)

生成器函数可以惰性地产出多个值,分为同步生成器和异步生成器。

3.1 同步生成器(sync* + yield

返回 Iterable,每次迭代时产生一个值。

dart

csharp 复制代码
Iterable<int> countUpTo(int n) sync* {
  for (int i = 1; i <= n; i++) {
    yield i;          // 产生一个值
  }
}

void main() {
  for (int val in countUpTo(5)) {
    print(val);
  }
}
// 输出:1 2 3 4 5

递归生成器:

csharp 复制代码
Iterable<int> countDown(int from) sync* {
  if (from <= 0) return;
  yield from;
  yield* countDown(from - 1);   // yield* 委托给另一个生成器
}

void main() {
  print(countDown(3).toList()); // [3, 2, 1]
}
3.2 异步生成器(async* + yield

返回 Stream,可异步产出值(支持 await)。

csharp 复制代码
Stream<int> asyncCount(int max) async* {
  for (int i = 1; i <= max; i++) {
    await Future.delayed(Duration(milliseconds: 500));
    yield i;
  }
}

void main() async {
  await for (int val in asyncCount(3)) {
    print(val);
  }
}
// 每0.5秒输出:1 2 3

使用 yield* 委托给另一个异步生成器:

arduino 复制代码
Stream<int> numbers() async* {
  yield 1;
  yield* asyncCount(2);   // 委托
  yield 4;
}

四、Future 高级组合操作

4.1 Future.wait:等待多个 Future 全部完成
dart 复制代码
Future<void> fetchUsers() async {
  var futures = [
    Future.delayed(Duration(seconds: 1), () => 'User 1'),
    Future.delayed(Duration(seconds: 2), () => 'User 2'),
    Future.delayed(Duration(seconds: 1), () => 'User 3'),
  ];

  List<String> results = await Future.wait(futures);
  print(results); // [User 1, User 2, User 3] 约2秒后输出
}
4.2 Future.any:任意一个完成即返回
dart 复制代码
Future<void> fastestResponse() async {
  var futures = [
    Future.delayed(Duration(seconds: 3), () => '慢速'),
    Future.delayed(Duration(seconds: 1), () => '快速'),
  ];
  String result = await Future.any(futures);
  print(result); // '快速'(约1秒后)
}
4.3 Future.timeout:超时处理
dart 复制代码
Future<void> withTimeout() async {
  try {
    String data = await Future.delayed(Duration(seconds: 5), () => '数据')
        .timeout(Duration(seconds: 2));
    print(data);
  } catch (e) {
    print('超时: $e');
  }
}
4.4 其他实用方法
方法 作用
Future.value 创建已完成的 Future
Future.error 创建立即出错的 Future
Future.delayed 延迟执行
Future.sync 同步执行,但返回 Future
Future.microtask 在微任务队列中执行(高优先级)

五、Stream 高级操作

5.1 转换 Stream
dart 复制代码
void main() async {
  Stream<int> source = Stream.fromIterable([1, 2, 3, 4]);

  // map
  Stream<int> doubled = source.map((x) => x * 2);
  await for (int val in doubled) print(val); // 2,4,6,8

  // where
  Stream<int> evens = source.where((x) => x.isEven);
  await for (int val in evens) print(val);   // 2,4

  // expand
  Stream<int> expanded = source.expand((x) => [x, x]);
  await for (int val in expanded) print(val); // 1,1,2,2,3,3,4,4
}
5.2 合并多个 Stream
arduino 复制代码
import 'dart:async';

void main() async {
  Stream<int> streamA = Stream.periodic(Duration(milliseconds: 300), (i) => i).take(3);
  Stream<int> streamB = Stream.periodic(Duration(milliseconds: 500), (i) => i * 10).take(3);

  // 合并(交错输出)
  Stream<int> merged = StreamGroup.merge([streamA, streamB]);
  await for (int val in merged) {
    print(val);
  }
  // 可能输出:0,0,1,10,2,20
}
5.3 其他常用 Stream 方法
方法 作用
stream.take(n) 取前 n 个事件
stream.skip(n) 跳过前 n 个事件
stream.takeWhile(condition) 满足条件时取,一旦不满足就结束
stream.distinct() 去重(连续重复的只发一次)
stream.handleError 处理错误并可能继续
stream.timeout 超时后触发错误或执行备选
stream.toList() 收集所有事件到 List(返回 Future<List>
stream.fold / stream.reduce 归约

六、错误处理

6.1 Future 中的错误
dart 复制代码
Future<void> risky() async {
  throw FormatException('格式错误');
}

void main() async {
  // try-catch 方式
  try {
    await risky();
  } on FormatException catch (e) {
    print('捕获: $e');
  }

  // catchError 方式
  risky().catchError((e) => print('catchError: $e'));
}
6.2 Stream 中的错误
dart 复制代码
void main() {
  Stream<int> stream = Stream.fromIterable([1, 2, 3])
      .map((x) {
        if (x == 2) throw Exception('值2非法');
        return x;
      })
      .handleError((e) {
        print('处理错误: $e');
        return -1; // 可返回替代值,但注意类型必须匹配
      });

  stream.listen(
    (val) => print('收到: $val'),
    onError: (err) => print('监听器收到错误: $err'),
  );
}
// 输出:
// 收到: 1
// 处理错误: Exception: 值2非法
// 监听器收到错误: Exception: 值2非法
// 注意:错误之后流通常终止,除非使用 `handleError` 且返回相同类型且流未取消

七、微任务与事件循环

Dart 事件循环有多个队列:微任务队列 (Microtask)和事件队列 (Event)。Future 默认放入事件队列,但可以通过 Future.microtask 放入微任务队列(优先级更高)。

dart

scss 复制代码
void main() {
  print('开始');

  Future(() => print('事件队列 Future'));
  Future.microtask(() => print('微任务 Future'));

  print('结束');
}
// 输出:
// 开始
// 结束
// 微任务 Future
// 事件队列 Future

注意:通常不需要手动使用微任务,但了解它有助于理解异步顺序。

八、总结与最佳实践
概念 适用场景 代码特征
Future 单次异步操作(如 HTTP 请求) async/await.then()
Stream 多次异步事件(如用户点击、定时器) async*/yieldStreamController
sync* 惰性同步序列 Iterable<T> + yield
async* 异步事件序列 Stream<T> + yield

元数据

1 内置注解

是指注解(Annotations) ,用于为代码添加额外信息(如 @deprecated@override),供工具或运行时使用

  • @override:表示子类方法覆盖了父类的方法。
  • @deprecated / @Deprecated('message'):标记元素已过时,使用时会发出警告。
  • @required(已废弃,使用 required 关键字代替)。
  • @doNotStore(用于 dart:ffi)。
  • @literal(用于预期为字面量的参数)。

2 自定义注解

你可以定义自己的注解类,只需要创建一个类,并在其前添加 @ 即可使用。通常将注解类的构造函数声明为 const

arduino 复制代码
// 定义注解类
class Todo {
  final String who;
  final String what;
  const Todo(this.who, this.what);
}

// 使用自定义注解
@Todo('Alice', 'Refactor this function')
void legacyFunction() {
  // ...
}

// 注解可以包含多个值,也可以单独使用(不需要参数)
class Experimental {
  const Experimental();
}

@Experimental()
void newFeature() {}

枚举

从 Dart 2.17 开始,枚举可以拥有字段构造函数方法getter。每个枚举值可以携带不同的数据。

1 带字段的枚举

scss 复制代码
enum Planet {
  mercury(0.4, 0.055),
  venus(0.7, 0.815),
  earth(1.0, 1.0),
  mars(1.5, 0.107);

  final double distance;   // 距离太阳(AU)
  final double mass;       // 质量(地球质量单位)

  // 构造函数必须是 const
  const Planet(this.distance, this.mass);

  // 计算属性
  double get gravity => mass / (distance * distance);
}

void main() {
  Planet earth = Planet.earth;
  print('地球距离: ${earth.distance} AU');  // 1.0
  print('地球质量: ${earth.mass}');        // 1.0
  print('地球重力: ${earth.gravity}');     // 1.0
}

2 带方法的枚举

dart 复制代码
enum Status {
  loading,
  success,
  failure;

  // 实例方法
  bool get isSuccess => this == Status.success;

  // 静态方法
  static Status fromString(String str) {
    return Status.values.firstWhere(
      (e) => e.name == str,
      orElse: () => throw ArgumentError('Unknown status: $str'),
    );
  }
}

void main() {
  Status s = Status.success;
  print(s.isSuccess);  // true

  Status parsed = Status.fromString('loading');
  print(parsed);       // Status.loading
}

3 实现接口的枚举

dart 复制代码
abstract class Describable {
  String describe();
}

enum Animal implements Describable {
  cat('meow'),
  dog('bark'),
  cow('moo');

  final String sound;
  const Animal(this.sound);

  @override
  String describe() => 'This animal says $sound';
}

void main() {
  Animal animal = Animal.cat;
  print(animal.describe());  // This animal says meow
}

4、枚举与 switch 语句

枚举最常见的使用场景是与 switch 配合,且推荐使用 switch 表达式(Dart 3)实现详尽检查。

dart 复制代码
enum TrafficLight { red, yellow, green }

String getAction(TrafficLight light) {
  return switch (light) {
    TrafficLight.red => 'Stop',
    TrafficLight.yellow => 'Caution',
    TrafficLight.green => 'Go',
  };
}

void main() {
  print(getAction(TrafficLight.red));     // Stop
  print(getAction(TrafficLight.green));   // Go
}

传统 switch 语句需要 break

php 复制代码
void handleLight(TrafficLight light) {
  switch (light) {
    case TrafficLight.red:
      print('Stop');
      break;
    case TrafficLight.yellow:
      print('Caution');
      break;
    case TrafficLight.green:
      print('Go');
      break;
  }
}

5、枚举的局限性

  1. 不能动态添加值:枚举值在编译时就固定。
  2. 不能继承或混入 :枚举类默认是 final,不能作为父类。
  3. 自动生成 values 列表,但顺序与声明顺序一致。
  4. 每个枚举值都是单例 ,因此可以使用 identical 比较。
ini 复制代码
enum Size { small, medium, large }

void main() {
  Size s1 = Size.small;
  Size s2 = Size.small;
  print(identical(s1, s2));  // true
}

6、枚举与泛型

枚举类型可以作为泛型参数:

scala 复制代码
enum Priority { low, medium, high }

class Task<T extends Enum> {
  final T priority;
  Task(this.priority);
}

void main() {
  Task<Priority> task = Task(Priority.high);
  print(task.priority);  // Priority.high
}

7、实用技巧

7.1 获取枚举值的下一个/上一个
scss 复制代码
enum Direction { north, east, south, west }

extension DirectionExtension on Direction {
  Direction get next {
    int nextIndex = (index + 1) % Direction.values.length;
    return Direction.values[nextIndex];
  }

  Direction get previous {
    int prevIndex = (index - 1 + Direction.values.length) % Direction.values.length;
    return Direction.values[prevIndex];
  }
}

void main() {
  Direction dir = Direction.north;
  print(dir.next);     // Direction.east
  print(dir.previous); // Direction.west
}
7.2 枚举与 JSON 序列化

枚举值通常序列化为字符串名称:

dart 复制代码
enum Role { admin, editor, viewer }

String roleToJson(Role role) => role.name;
Role roleFromJson(String json) => Role.values.firstWhere((e) => e.name == json);

一、基本导入语法

1.1 导入核心库(dart: 前缀)

Dart 平台内置的核心库,如 dart:core(自动导入)、dart:mathdart:iodart:convertdart:async 等。

dart

arduino 复制代码
import 'dart:math';      // 导入数学库
import 'dart:io';        // 导入文件/网络/进程库(仅命令行/Flutter 桌面端)
import 'dart:convert';   // 导入 JSON/UTF-8 编解码库
import 'dart:async';     // 导入异步库(Future/Stream 已自动部分导入,但显式导入无害)

void main() {
  print(sqrt(16)); // 来自 dart:math,输出 4.0
}

1.2 导入自己写的文件(相对路径或绝对路径)

dart

arduino 复制代码
// 假设项目结构:
// lib/
//   utils/
//     math_helper.dart
//   main.dart

// 在 main.dart 中:
import 'utils/math_helper.dart';   // 相对路径
import '/absolute/path/to/file.dart'; // 绝对路径(不推荐)

1.3 导入第三方包(package: 前缀)

通过 pubspec.yaml 添加依赖后,使用 package:包名/路径 导入。

dart

dart 复制代码
// 假设 pubspec.yaml 中有:dependencies: http: ^0.13.0
import 'package:http/http.dart' as http;

void fetch() async {
  var response = await http.get(Uri.parse('https://api.example.com'));
  print(response.body);
}

二、导入时的修饰符

2.1 使用 as 指定前缀(避免命名冲突)

当两个库有相同名称的类时,可以使用前缀区分。

dart

javascript 复制代码
import 'package:math_expressions/math_expressions.dart' as mathExpr;
import 'dart:math' as math;

void main() {
  // math.sin 来自 dart:math
  print(math.sin(3.14159 / 2)); // 1.0

  // mathExpr.Parser 来自第三方包
  var parser = mathExpr.Parser();
}

2.2 使用 show 只导入部分成员

dart

perl 复制代码
// 只导入 sqrt 和 sin,其他如 cos、max 等不可见
import 'dart:math' show sqrt, sin;

void main() {
  print(sqrt(25));   // 5.0
  // print(cos(0));  // 错误:cos 未导入
}

2.3 使用 hide 排除部分成员

dart

perl 复制代码
// 导入所有成员,但排除 sqrt
import 'dart:math' hide sqrt;

void main() {
  print(sin(0));     // 0.0
  // print(sqrt(4)); // 错误:sqrt 被隐藏
}

2.4 组合使用

可以同时使用 showas,但通常 show/hideas 不常混用(容易混淆)。一般建议:避免冲突用 as,限制导入用 show

dart

arduino 复制代码
import 'dart:math' as math show sin, cos;

三、库的可见性与 part / export

3.1 私有成员(_ 下划线)

Dart 中以下划线开头的标识符在库内可见 ,库外不可见。每个 .dart 文件是一个独立的库。

dart

java 复制代码
// 文件:math_utils.dart
int _internalCounter = 0;      // 库私有

int add(int a, int b) => a + b; // 公开

class Calculator {
  void _reset() {}              // 私有方法
}

dart

csharp 复制代码
// 文件:main.dart
import 'math_utils.dart';

void main() {
  print(add(1, 2));            // 可以
  // print(_internalCounter);   // 错误:私有
}

3.2 part 指令(拆分一个库到多个文件)

part 允许将一个库拆分为多个文件,所有 part 文件共享同一个库的私有作用域。不推荐使用 ,建议使用 export 和独立库代替。

dart

dart 复制代码
// 文件:my_library.dart
library my_library;          // 显式声明库名(可选)
part 'utils.dart';
part 'models.dart';

// 公开 API
void doSomething() => _helper();

dart

javascript 复制代码
// 文件:utils.dart(与 my_library.dart 在相同目录)
part of my_library;

void _helper() {
  print('内部帮助函数');
}

现在使用 import 'my_library.dart' 即可同时获得 utils.dartmodels.dart 中的内容(且私有成员互通)。

3.3 export 指令(组合多个库为一个公开 API)

export 用于创建一个"聚合库",将多个库的公开成员重新导出。

dart

dart 复制代码
// 文件:shapes.dart
export 'circle.dart';
export 'rectangle.dart' hide RectanglePrivateHelper; // 可选择性隐藏
export 'triangle.dart' show Triangle;

使用者只需 import 'shapes.dart' 即可获得三个库的所有公开成员。


四、延迟加载(Deferred Loading)

使用 deferred as 可以实现按需加载,减少应用初始启动时间。仅在 Web 和 Dart VM 中支持(Flutter 不支持延迟加载)。

dart

csharp 复制代码
import 'package:heavy/analysis.dart' deferred as heavy;

Future<void> runAnalysis() async {
  // 手动触发库的加载
  await heavy.loadLibrary();
  var analyzer = heavy.Analyzer();
  analyzer.analyze();
}

注意:

  • 延迟加载的库只能通过 loadLibrary() 加载一次,之后可重复使用。
  • 加载返回 Future,可以等待完成。
  • 延迟库中的常量、类型注解等不能在使用前直接引用(因为尚未加载)。

五、内置核心库列表

库名 功能 常用类/函数
dart:core 自动导入,基础类型 String, int, List, Map, print()
dart:async 异步编程 Future, Stream, Timer
dart:math 数学函数 sqrt, sin, Random, Point
dart:convert 编解码 jsonDecode, jsonEncode, utf8
dart:io 文件/网络/进程(非 Web) File, HttpClient, Process
dart:html 浏览器 API(Web 专用) document, window, WebSocket
dart:typed_data 高效字节数组 Uint8List, ByteBuffer
dart:ffi 调用 C 语言接口 Pointer, DynamicLibrary
dart:isolate 并发(Isolate) Isolate.spawn, ReceivePort
dart:mirrors 反射(VM 专用,Flutter 禁用) reflect, ClassMirror
相关推荐
yuki_uix2 小时前
HTTP 缓存策略:新鲜度与速度的权衡艺术
前端·面试
ZC跨境爬虫2 小时前
3D 地球卫星轨道可视化平台开发 Day5(简介接口对接+规划AI自动化卫星数据生成工作流)
前端·人工智能·3d·ai·自动化
毛骗导演2 小时前
Claude Code Agent 实现原理深度剖析
前端·架构
小蜜蜂嗡嗡2 小时前
flutter 自定义走马灯,内部为Widget控件的走马灯效果二:横向无限匀速滚动+每个Item与屏幕左侧对齐时,停靠3秒再继续滚动
开发语言·flutter
星晨雪海2 小时前
若依框架原有页面功能进行了点位管理模块完整改造(3)
开发语言·前端·javascript
morethanilove2 小时前
新建vue3 + ts +vite 项目
前端·javascript·vue.js
GISer_Jing2 小时前
微软AI战略全景:从基础设施到智能体生态
前端·人工智能·microsoft
发际线向北2 小时前
0x03 单元测试与Junit
前端·单元测试
忆往wu前2 小时前
搞懂 SPA 再学路由!Vue Router 从0到完善 + 嵌套路由一次性梳理
前端·vue.js