Skip to content

Dart 速查表

为任意平台快速应用而优化的客户端语言。

01

入门

Hello World 与注释

main() 是每个 Dart 程序的入口。使用 // 单行注释、/* */ 块注释、/// 文档注释。文档注释支持 Markdown,被 dartdoc 用于生成 API 文档。

dart
// Hello World in Dart
void main() {
  print('Hello, World!');
}

/* Multi-line
   block comment */

/// Documentation comment
/// Supports Markdown
void greet(String name) {
  print('Hi, $name');
}

变量:var、final、const

用 var 进行类型推断,final 用于运行时常量(只赋值一次),const 用于编译时常量。const 更严格——DateTime.now() 不能是 const,因为其值在编译时未知。值不会改变时优先用 final/const 而非 var。

dart
var name = 'Alice';        // type inferred
String city = 'NYC';        // explicit type
final age = 30;             // runtime constant
const PI = 3.14;            // compile-time constant
final now = DateTime.now(); // OK: runtime value
// const time = DateTime.now(); // ERROR: not compile-time

const list = [1, 2, 3];     // const list (immutable)
final list2 = [4, 5, 6];    // final ref, mutable content

空安全基础

Dart 具有健全的空安全。在类型后加 ? 使其可空(int? 可为 null;int 不能)。用 ?? 提供默认值,??= 在为空时赋值,?. 安全访问,! 断言非空。非空类型保证不为 null,在编译期消除 NullPointerException。

dart
int value = 42;              // non-nullable
int? nullableValue;          // nullable (default null)
print(nullableValue);        // null
print(nullableValue ?? 0);   // 0 (null coalescing)
nullableValue ??= 10;        // assign if null
print(nullableValue);        // 10

// int x = null;            // ERROR: non-nullable
String name = 'Alice';
print(name.length);          // safe, no null check

输出与输入

print() 输出到 stdout 并换行,只接受一个参数。用 stdout.write() 不换行。stdin.readLineSync() 从 stdin 读取一行并返回可空 String。dart:io 在 web 平台不可用。

dart
import 'dart:io';

void main() {
  print('Hello');                    // stdout with newline
  stdout.write('no newline');        // no trailing newline
  stderr.writeln('an error');        // stderr with newline

  String? input = stdin.readLineSync(); // read a line
  int? n = int.tryParse(input ?? '');   // safe parse
  print('You entered: $n');
}

类型推断与 dynamic

var 让 Dart 在编译期推断类型(类型之后不可变)。dynamic 禁用静态类型检查——慎用。Object 是所有非空类型的父类型。用 as 转型,但优先用类型安全的检查。dynamic 是唯一允许在编译期调用未知方法的类型。

dart
var x = 10;          // inferred as int
var y = 3.14;        // inferred as double
var s = 'hi';        // inferred as String

dynamic d = 10;      // type can change
d = 'now string';    // OK
// d.foo();          // compiles, may fail at runtime

Object o = 'hello';  // supertype of all non-null types
// o.length;         // ERROR: Object has no length
print((o as String).length);  // 5 (cast)
02

数据类型

数字类型(int、double、num)

num 是 int 和 double 的父类型。~/ 是整数除法,% 或 remainder() 取模。/ 即使对 int 也总返回 double。int 有 bitLength、toRadixString();double 有 toStringAsFixed()。在 web 平台所有数字编译为 JS 数字。

dart
int a = 42;
double b = 3.14;
num c = 10;     // num is supertype of int & double
num d = 2.71;
print(a.bitLength);          // 6
print(b.toStringAsFixed(2)); // '3.14'
print(10 ~/ 3);             // 3 (integer division)
print(10.remainder(3));     // 1
print(0xFF);                // 255 (hex)
print(1.5e3);               // 1500.0 (scientific)

字符串

字符串是不可变的 UTF-16 码元序列。单引号与双引号可互换。三引号允许多行。$var 插值变量,${expr} 插值表达式。r 前缀创建原始字符串(不处理转义)。插值优于拼接。

dart
var s1 = 'single';
var s2 = "double";
var s3 = '''multi
line string''';
var name = 'Alice';
print('Hi, $name');               // interpolation
print('Length: ${name.length}'); // expression interpolation

var raw = r'No escape: \n';      // raw string (literal)
var escaped = 'It\'s ok';        // escaped quote
print(s1[0]);                     // 's' (index access)

布尔类型

Dart 只有两个 bool 值:true 和 false。与 JavaScript 不同,没有 truthy/falsy 强制转换——条件必须是真正的 bool。对集合和字符串用 .isNotEmpty / .isEmpty 而非依赖 truthiness。

dart
bool isTrue = true;
bool isFalse = false;
print(!isTrue);           // false
print(isTrue && isFalse); // false
print(isTrue || isFalse); // true

// Only bool is allowed in conditions; no truthy/falsy
// if (1) {} // ERROR: must be bool
if ('text'.isNotEmpty) {
  print('non-empty');
}

列表(List)

列表是有序、可增长的集合(类似数组)。用 <Type>[] 指定元素类型。用 const 创建不可变列表。展开运算符 ... 将列表展开到另一个列表。列表使用从零开始的索引,是 Dart 最常用的集合。

dart
var list = [1, 2, 3];
var typed = <String>['a', 'b'];
var constList = const [1, 2, 3];
list.add(4);
list.addAll([5, 6]);
print(list.length);          // 6
print(list[0]);              // 1
print(list.sublist(1, 3));   // [2, 3]
var spread = [...list, 7];   // spread
// constList.add(0);         // ERROR: immutable

映射(Map)

Map 是键值对。键和值可为任意类型;用 <KeyType, ValueType>{} 指定。通过 map[key] 访问,键不存在时返回 null——用 ?? 提供默认值。Map 保持插入顺序。entries、keys、values 暴露可迭代对象。

dart
var map = {
  'name': 'Alice',
  'age': 30,
};
var typed = <String, int>{'a': 1, 'b': 2};
map['city'] = 'NYC';              // add entry
print(map['name']);               // Alice
print(map.length);                // 3
print(map.containsKey('age'));    // true
map.forEach((k, v) => print('$k: $v'));
var keys = map.keys.toList();     // [name, age, city]

集合(Set)与 Runes

Set 是无序的唯一元素集合——适用于去重和集合运算(并、交、差)。Runes 暴露字符串的 Unicode 码点,处理 emoji 和以代理对存储的非 BMP 字符时需要。

dart
var set = {1, 2, 3};
set.add(2);              // no duplicate added
set.add(4);
print(set);              // {1, 2, 3, 4}
print(set.contains(2));  // true
print(set.intersection({2, 3, 5})); // {2, 3}

// Runes (Unicode code points)
var heart = '♥';        // ♥
print(heart);                // ♥
print('A'.codeUnitAt(0));    // 65
03

运算符

算术运算符

~/ 是 Dart 的整数除法运算符(截断为 int)。% 取模。/ 即使对 int 也返回 double。算术运算符适用于 num、int、double。需要整数除法结果时用 ~/。

dart
print(5 + 3);   // 8
print(5 - 3);   // 2
print(5 * 3);   // 15
print(5 / 3);   // 1.6666... (double)
print(5 ~/ 3);  // 1 (integer division)
print(5 % 3);   // 2 (modulo)
print(-(5));    // -5 (unary minus)
print(2.toString()); // '2'

自增与自减

++ 和 -- 有前缀和后缀形式。后缀(i++)返回原值后自增;前缀(++i)自增后返回新值。行为与 C/Java 一致。避免在复杂表达式中混用以提高可读性。

dart
var i = 5;
print(i++);  // 5 (postfix: use, then add)
print(i);    // 6
print(++i);  // 7 (prefix: add, then use)
print(i--);  // 7
print(--i);  // 5

关系与相等运算符

== 对内置类型(字符串、数字)比较值。对列表和多数对象,== 默认比较引用,除非重写。用 package:collection 或 flutter/foundation.dart 的 listEquals() 做集合深度相等比较。

dart
print(3 == 3);   // true
print(3 != 4);   // true
print(3 < 4);    // true
print(3 > 4);    // false
print(3 <= 3);   // true
print(3 >= 4);   // false
print('a' == 'a'); // true (content equality)
var l1 = [1, 2];
var l2 = [1, 2];
print(l1 == l2); // false (reference equality)

逻辑运算符

&&(与)、||(或)、!(非)只对 bool 操作。&& 和 || 都短路:&& 在左侧为 false 时停止,|| 在左侧为 true 时停止。与某些语言不同,操作数必须是 bool——没有 truthy/falsy 强制。

dart
bool a = true, b = false;
print(a && b);  // false
print(a || b);  // true
print(!a);      // false

// short-circuit evaluation
bool check() { print('called'); return true; }
false && check();  // check() NOT called
true || check();   // check() NOT called

类型测试:is、as

is 在运行时检查类型,并在分支内启用智能转换(无需显式转型)。is! 是否定形式。as 执行不安全转型,类型不匹配时抛出 TypeError。为安全起见优先用 is 检查而非 as 转型。

dart
Object x = 'hello';
print(x is String);   // true
print(x is! int);     // true
if (x is String) {
  print(x.length);    // smart-cast to String
}
Object y = 42;
print((y as int) + 1); // 43 (cast)
// (y as String);      // runtime TypeError

级联(..)与空感知运算符

.. 是级联运算符——调用方法或设置字段并返回原对象,无需重复变量即可流畅链式调用。?. 是空感知访问运算符(目标为 null 时返回 null)。与 ?? 结合提供默认值。

dart
class Builder {
  String? name;
  int? size;
  Builder setName(String n) { name = n; return this; }
  Builder setSize(int s) { size = s; return this; }
}

var b = Builder()
  ..setName('widget')
  ..setSize(10);
print(b.name); // widget

String? path;
print(path?.length);     // null (safe access)
print(path?.length ?? 0); // 0
04

控制流

if-else

if-else 条件必须求值为 bool——没有 truthy/falsy。三元运算符 ?: 与其他 C 族语言相同。else-if 链很常见。Dart 3 还支持在集合字面量内用 if 作为表达式(collection-if)。

dart
int score = 85;
if (score >= 90) {
  print('A');
} else if (score >= 80) {
  print('B');
} else {
  print('C');
}

// ternary expression
var grade = score >= 60 ? 'pass' : 'fail';
print(grade); // pass

// if (score) {} // ERROR: condition must be bool

for 与 for-in

经典 C 风格 for 循环用 init; condition; update。for-in 迭代任何 Iterable。对 Map 迭代 .entries、.keys 或 .values。不需要索引时优先用 for-in。${entry.key} 使用表达式插值。

dart
for (var i = 0; i < 3; i++) {
  print(i);
}

var list = ['a', 'b', 'c'];
for (var item in list) {
  print(item);
}

// for-in with Map entries
var map = {'x': 1, 'y': 2};
for (var entry in map.entries) {
  print('${entry.key}: ${entry.value}');
}

while 与 do-while

while 在每次迭代前检查条件;do-while 在之后检查,因此循环体至少执行一次。两者都要求 bool 条件。迭代次数未知时用 while。

dart
var i = 0;
while (i < 3) {
  print('while $i');
  i++;
}

var j = 0;
do {
  print('do $j');
  j++;
} while (j < 3);

switch(Dart 3)

Dart 3 引入了 switch 表达式,返回值并每个 case 用 =>。模式支持 OR(||)、AND(&&)、关系(>=)和通配符(_)。经典 switch 语句需要 break(无 fall-through)。密封类型和枚举要求穷尽匹配。

dart
// Classic switch statement
var color = 'red';
switch (color) {
  case 'red':
    print('stop');
    break;
  case 'green':
    print('go');
    break;
  default:
    print('unknown');
}

// Dart 3 switch expression
String describe(int n) => switch (n) {
  0 => 'zero',
  1 || 2 => 'small',
  >= 3 && <= 10 => 'medium',
  _ => 'large',
};
print(describe(5)); // medium

break 与 continue

break 退出最近的循环;continue 跳到下一次迭代。标签(outer:)允许 break/continue 指向外层循环——慎用因其降低可读性。break 也用于结束 switch case。

dart
for (var i = 0; i < 5; i++) {
  if (i == 2) continue; // skip 2
  if (i == 4) break;    // stop at 4
  print(i);  // 0, 1, 3
}

// labels for nested loops
outer:
for (var i = 0; i < 3; i++) {
  for (var j = 0; j < 3; j++) {
    if (i + j > 2) break outer;
    print('$i,$j');
  }
}

断言(assert)

assert(condition, message) 在开发期间检查条件。断言在 debug 模式启用,在 production(release)构建中移除。用于内部不变量和调试——不用于必须在生产中运行的输入校验。

dart
void setAge(int age) {
  assert(age >= 0, 'age must be non-negative');
  // ...
}

assert(1 == 1);  // passes in debug
// assert(1 == 2, 'math is broken'); // fails in debug

// Assertions are stripped in release/production builds
05

函数

函数声明

在 Dart 中,函数是一等对象——可赋值给变量、作为参数传递、被返回。int Function(int) 等类型别名描述函数类型。函数声明包含可选返回类型和参数类型。

dart
// named function with return type
int add(int a, int b) {
  return a + b;
}

// functions are first-class objects
int Function(int) makeAdder(int n) {
  return (int x) => x + n;
}

var add5 = makeAdder(5);
print(add5(3)); // 8

print(add(2, 3)); // 5

箭头函数

=>(箭头)语法是 { return expr; } 的简写,用于单表达式函数。常用于简短方法、getter 和回调。箭头函数在不牺牲类型安全的前提下让代码简洁。

dart
// single-expression function with =>
int square(int x) => x * x;
String greet(String name) => 'Hi, $name';

// arrow with nullable
String? firstChar(String? s) => s?.isEmpty ?? true ? null : s[0];

print(square(4));      // 16
print(greet('Al'));    // Hi, Al
print(firstChar('hi')); // h

可选位置参数

可选位置参数用方括号 [] 包裹,必须位于必填参数之后。默认为 null(或提供的默认值)。当参数顺序直观明显时使用。

dart
// optional positional params wrapped in []
String greet(String name, [String? title]) {
  if (title != null) {
    return 'Hello, $title $name';
  }
  return 'Hello, $name';
}

print(greet('Alice'));        // Hello, Alice
print(greet('Bob', 'Dr.'));   // Hello, Dr. Bob

命名参数

命名参数用 {} 包裹,在调用处按名称传递(顺序无关)。用 required 标记必填的命名参数。命名参数默认为 null,除非给定默认值。适用于有大量可选参数的 API。

dart
// named params wrapped in {}, required marks mandatory
void createUser({
  required String name,
  int age = 0,
  String? email,
}) {
  print('$name, $age, $email');
}

createUser(name: 'Alice', age: 30);
createUser(name: 'Bob', email: '[email protected]');

// named params are passed by name, order-independent

默认值

可选位置参数和命名参数都可以用 = 指定默认值。默认值必须是编译时常量。默认值通过在存在合理默认值时避免 null,使可选参数更安全。

dart
// default values for optional positional
double calc(double a, [double b = 1.0, double c = 0.0]) {
  return a * b + c;
}

// default values for named
void config({String host = 'localhost', int port = 8080}) {
  print('$host:$port');
}

print(calc(5));       // 5.0
print(calc(5, 2));    // 10.0
config(port: 3000);   // localhost:3000

匿名函数与闭包

匿名函数(lambda)没有名称,常赋值给变量或作为回调传递。闭包捕获其外围作用域的变量并保持其存活。参数列表可带类型(int a, int b)或不带类型(a, b)。

dart
// anonymous function assigned to a variable
var multiply = (int a, int b) => a * b;
print(multiply(3, 4)); // 12

// closure capturing a variable
Function counter() {
  int count = 0;
  return () => ++count;
}

var c = counter();
print(c()); // 1
print(c()); // 2

// used as callbacks
[1, 2, 3].forEach((n) => print(n));
06

类与对象

类定义

类将字段(状态)和方法(行为)组合在一起。构造函数 Person(this.name, this.age) 使用参数初始化简写直接赋值字段。与 Java 不同,Dart 类只有一个构造函数体;用命名构造函数创建变体。

dart
class Person {
  String name;
  int age;

  // constructor
  Person(this.name, this.age);

  void greet() {
    print('Hi, I am $name');
  }
}

var p = Person('Alice', 30);
p.greet(); // Hi, I am Alice
print(p.name); // Alice

实例变量与 this

实例变量(字段)保存每个对象的状态。this 仅在需要与参数区分或为清晰时使用。所有非空字段必须被初始化——通过构造函数初始化器、this.x 简写或默认值。字段会生成隐式 getter/setter。

dart
class Point {
  double x;
  double y;

  Point(this.x, this.y);

  double distanceTo(Point other) {
    return ((x - other.x) * (x - other.x) +
            (y - other.y) * (y - other.y));
  }

  void moveBy(double dx, double dy) {
    this.x += dx;   // 'this' is optional when unambiguous
    y += dy;
  }
}

Getter 与 Setter

用 get/set 关键字定义的 getter 和 setter,让你向调用者暴露看起来像字段的计算属性。用于校验输入或计算派生值而不改变公共 API。final 字段不能有 setter。

dart
class Rectangle {
  double width, height;
  Rectangle(this.width, this.height);

  // computed getter
  double get area => width * height;
  set size(double v) {
    width = v;
    height = v;
  }
}

var r = Rectangle(3, 4);
print(r.area);  // 12 (accessed like a field)
r.size = 10;
print(r.area);  // 100

静态成员

静态成员(字段和方法)属于类本身而非实例。通过 ClassName.member 访问。用于在所有实例间共享的工具函数和常量。静态成员在没有实例的情况下不能引用非静态成员。

dart
class MathUtils {
  static const double PI = 3.14159;
  static double circleArea(double r) => PI * r * r;
}

// accessed via the class, not an instance
print(MathUtils.PI);             // 3.14159
print(MathUtils.circleArea(2));  // 12.566

// static members belong to the class, not instances

工厂构造函数

工厂构造函数(factory 关键字)不一定创建新实例——可返回缓存实例、子类型或预构建对象。适用于单例、缓存和返回子类。工厂构造函数像普通构造函数一样使用类名。

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

  // factory may return a cached instance
  factory Logger(String name) {
    return _cache.putIfAbsent(name, () => Logger._internal(name));
  }

  Logger._internal(this.name);
}

var a = Logger('app');
var b = Logger('app');
print(identical(a, b)); // true (same cached instance)

Object 与 toString

每个类都继承自 Object,后者提供 toString()、==、hashCode 和 runtimeType。重写 toString() 以便调试可读。如果重写了 ==,也必须重写 hashCode 以维护在 Map 和 Set 中使用的契约。

dart
class Book {
  String title;
  Book(this.title);

  @override
  String toString() => 'Book($title)';

  @override
  bool operator ==(Object other) =>
      other is Book && other.title == title;

  @override
  int get hashCode => title.hashCode;
}

print(Book('Dart')); // Book(Dart)
print(Book('A') == Book('A')); // true
07

构造函数

默认与生成构造函数

如果不定义任何构造函数,Dart 提供默认的无参构造函数。生成构造函数创建新实例。参数中的 this.field 简写直接赋值字段。一旦定义了构造函数,默认构造函数就不再生成。

dart
class Animal {
  String species;

  // generative constructor
  Animal(this.species);

  // if no constructor is written, Dart provides:
  // Animal() : species = 'unknown';
}

var a = Animal('cat');
print(a.species); // cat

命名构造函数

命名构造函数(ClassName.name)让一个类有多种创建模式。初始化列表(: field = value)在构造函数体之前运行,可初始化 final 字段。适用于工厂式创建和转换构造函数。

dart
class Point {
  double x, y;
  Point(this.x, this.y);

  // named constructor
  Point.origin() : x = 0, y = 0;
  Point.fromList(List<double> l) : x = l[0], y = l[1];
}

var p1 = Point.origin();
var p2 = Point.fromList([3, 4]);
print('${p1.x},${p1.y}'); // 0.0,0.0
print('${p2.x},${p2.y}'); // 3.0,4.0

初始化列表

初始化列表(: expr, expr)在构造函数体之前运行,可设置 final 字段并运行断言。这是初始化 final 字段的唯一位置(this.field 简写除外)。用于校验和计算字段值。

dart
class Temperature {
  final double celsius;

  // initializer list runs before body
  Temperature(double c) : celsius = c;
  Temperature.fromFahrenheit(double f)
      : celsius = (f - 32) * 5 / 9;

  // assert in initializer list
  Temperature.clamped(double c)
      : assert(c >= -273.15),
        celsius = c < -273.15 ? -273.15 : c;
}

print(Temperature.fromFahrenheit(32).celsius); // 0.0

重定向构造函数

重定向构造函数用 this(...) 转发到同类中的另一个构造函数。它没有自己的函数体和初始化列表。用于提供委托给主构造函数的便捷构造函数。

dart
class Point {
  double x, y;
  Point(this.x, this.y);

  // redirect to another constructor with 'this'
  Point.alongX(double x) : this(x, 0);
  Point.origin() : this(0, 0);
  Point.fromDouble(double n) : this.alongX(n);
}

print(Point.alongX(5).y); // 0.0
print(Point.origin().x);  // 0.0

常量构造函数

const 构造函数(const ClassName)创建编译时常量实例。所有字段必须为 final。相同的 const 实例会被规范化(共享)。const 构造函数支持深度不可变对象和编译时常量集合。

dart
class ImmutablePoint {
  final double x;
  final double y;

  // const constructor: creates compile-time constant instances
  const ImmutablePoint(this.x, this.y);

  static const origin = ImmutablePoint(0, 0);
}

const p = ImmutablePoint(1, 2);
const o = ImmutablePoint.origin;
print(identical(o, ImmutablePoint(0, 0))); // true

工厂与缓存

工厂构造函数可返回 const 或缓存实例,并可使用 Dart 3 switch 表达式。它们不同于总是创建新实例的生成构造函数。当构造逻辑必须选择返回什么时使用工厂。

dart
class Shape {
  final String type;
  const Shape._(this.type);

  factory Shape(String kind) {
    return switch (kind) {
      'circle' => const Shape._('circle'),
      'square' => const Shape._('square'),
      _ => const Shape._('unknown'),
    };
  }
}

print(Shape('circle').type); // circle
print(identical(Shape('circle'), Shape('circle'))); // true
08

继承

extends 与 super

用 extends 继承单一父类(Dart 是单继承)。子类构造函数必须调用父类构造函数(通常通过初始化列表中的 super(...))。用 @override 标记替换父类实现的方法。

dart
class Animal {
  String name;
  Animal(this.name);

  void speak() => print('$name makes a sound');
}

class Dog extends Animal {
  Dog(String name) : super(name);

  @override
  void speak() => print('$name barks');
}

var d = Dog('Rex');
d.speak(); // Rex barks

@override 与 super 调用

@override 是元数据注解,标记方法覆盖父类成员。它触发编译期检查父类方法是否存在。super.method() 从被覆盖方法内调用父类实现,允许扩展行为。

dart
class Vehicle {
  int speed = 0;
  void accelerate(int by) => speed += by;
  void describe() => print('Vehicle at $speed');
}

class Car extends Vehicle {
  @override
  void accelerate(int by) {
    super.accelerate(by);  // call parent method
    print('Car now at $speed');
  }
}

Car().accelerate(10); // Car now at 10

构造函数链

在继承中,父类构造函数在子类函数体之前运行。初始化列表中的 super(...) 调用向上传递参数。执行顺序:初始化列表 -> 父类构造函数 -> 本类构造函数体。这保证父类先被完全初始化。

dart
class Base {
  String tag;
  Base(this.tag) {
    print('Base created: $tag');
  }
}

class Derived extends Base {
  Derived(String tag) : super(tag) {
    print('Derived created');
  }
}

// order: super initializer -> super body -> derived body
Derived('x');
// Base created: x
// Derived created

noSuchMethod

noSuchMethod 在代码尝试调用对象上不存在的方法或访问不存在的字段时被调用。重写它可实现动态代理或 mock 对象。返回值让调用继续;默认抛出 NoSuchMethodError。

dart
class Proxy implements Object {
  @override
  dynamic noSuchMethod(Invocation inv) {
    print('Called: ${inv.memberName}');
    return null;
  }
}

var p = Proxy();
p.someMissingMethod(); // Called: Symbol("someMissingMethod")

密封类(Dart 3)

密封类(Dart 3)是封闭层次结构:所有直接子类型必须在同一库中。编译器对密封类型的子类型强制穷尽 switch 匹配,使模式匹配安全。非常适合建模固定的变体集合(如 Result/Either 类型)。

dart
sealed class Shape {
  const Shape();
}

class Circle extends Shape {
  final double r;
  const Circle(this.r);
}
class Square extends Shape {
  final double s;
  const Square(this.s);
}

// switch is exhaustive over sealed subtypes
double area(Shape s) => switch (s) {
  Circle(:var r) => 3.14 * r * r,
  Square(:var s) => s * s,
};

print(area(const Circle(2))); // 12.56
09

抽象类与接口

抽象类

抽象类(abstract class)不能直接实例化,可包含抽象方法(无函数体)由子类实现。也可有具体方法。用抽象类定义子类共享的部分实现。

dart
abstract class Animal {
  // abstract method: no body, must be overridden
  void makeSound();

  // concrete method: inherited as-is
  void breathe() => print('breathing');
}

class Cat extends Animal {
  @override
  void makeSound() => print('meow');
}

var c = Cat();
c.makeSound(); // meow
// Animal();    // ERROR: cannot instantiate abstract class

隐式接口

每个 Dart 类都隐式定义一个接口,包含其所有成员。用 implements 实现该接口——必须为每个成员提供函数体(不继承代码)。一个类可实现多个接口,用逗号分隔。

dart
class Television {
  void turnOn() => print('on');
  void turnOff() => print('off');
}

// every class implicitly defines an interface
class SmartTV implements Television {
  @override
  void turnOn() => print('smart on');
  @override
  void turnOff() => print('smart off');
}

SmartTV().turnOn(); // smart on

实现多个接口

与 extends 只允许一个父类不同,一个类可实现多个接口。用 implements 只继承类型契约——每个成员都必须重写,但可实现多个接口。这实现了类型的「多继承」。

dart
abstract class Flyer {
  void fly();
}
abstract class Swimmer {
  void swim();
}

// implement multiple interfaces
class Duck implements Flyer, Swimmer {
  @override
  void fly() => print('flying');
  @override
  void swim() => print('swimming');
}

Duck().fly();   // flying
Duck().swim();  // swimming

extends 与 implements 对比

extends 复用父类实现(单继承)。implements 只继承类型契约——每个成员都必须重新实现,但可实现多个接口。需要代码复用的「是一个」关系用 extends;「能做」契约用 implements。

dart
class Base {
  void greet() => print('hello');
  void wave() => print('waving');
}

// extends: reuse implementation, single parent
class A extends Base {
  @override
  void greet() => print('A says hi');
}

// implements: contract only, must override all
class B implements Base {
  @override
  void greet() => print('B says hi');
  @override
  void wave() => print('B waving');
}

抽象类与接口使用指南

用抽象类在紧密相关的类型间共享实现(代码继承)。用接口(通过 implements 使用的抽象类)定义不相关类型可满足的能力或契约。Dart 合并了这些概念:抽象类可同时充当两者。

dart
// Abstract class: share code among related types
abstract class Repository<T> {
  T? find(int id);          // abstract
  void save(T item) => print('saved'); // shared
}

// Interface: define a capability
abstract class Comparable<T> {
  int compareTo(T other);
}

class Product extends Repository<Product>
    implements Comparable<Product> {
  @override
  Product? find(int id) => null;
  @override
  int compareTo(Product other) => 0;
}
10

Mixin

定义 Mixin

mixin(mixin 关键字)是一个可复用的行为单元,可用 with 混入类中。mixin 不能有构造函数,不能直接实例化。适合在不相关的类间共享横向功能。

dart
mixin Greeter {
  String get name;

  void greet() => print('Hello, $name!');
}

class User with Greeter {
  @override
  String name;
  User(this.name);
}

User('Alice').greet(); // Hello, Alice!

使用 Mixin(with)

用 with 关键字后跟一个或多个 mixin(逗号分隔)应用 mixin。一个类可混入多个 mixin,当 mixin 互相覆盖时顺序很重要。mixin 线性地应用到类层次结构中。

dart
mixin Walker {
  void walk() => print('walking');
}
mixin Talker {
  void talk() => print('talking');
}

// mix in multiple mixins
class Person extends Object with Walker, Talker {
  String name;
  Person(this.name);
}

var p = Person('Bob');
p.walk(); // walking
p.talk(); // talking

mixin 约束(on)

on 子句将 mixin 约束为只能用于继承(或实现)指定父类型的类。这让 mixin 能调用该父类型的方法。约束类型充当 mixin 行为的必需基类。

dart
mixin Musician on Performer {
  void playNote() => print('playing note');
}

abstract class Performer {
  void perform();
}

class Singer extends Performer with Musician {
  @override
  void perform() => print('singing');
}

Singer().playNote(); // playing note
// Musician can only be mixed into Performer subtypes

mixin class(Dart 3)

Dart 3 引入了 mixin class,既可作为 mixin(with)使用,也可作为普通类(extends/implements)。普通 mixin 不能被 extends,普通 class 不能被 with——mixin class 桥接两者。适用于需要同时具备正常类语义的共享行为。

dart
// 'mixin class' can be both extended and mixed in
mixin class Counter {
  int _count = 0;
  int get count => _count;
  void increment() => _count++;
}

class App extends Counter {}
class Tool with Counter {}

print(App().count); // 0
App().increment();
Tool().increment();
print(App().count); // 0 (separate instance)

mixin 方法解析

当多个 mixin 定义相同方法时,with 子句中最右(最后)的 mixin 胜出,因为 mixin 从左到右应用,后面的覆盖前面的。类自身的方法覆盖所有 mixin。这种线性化决定方法解析。

dart
mixin A {
  void hello() => print('A');
}
mixin B {
  void hello() => print('B');
}

class X with A, B {}
class Y with B, A {}

X().hello(); // B (later mixin wins)
Y().hello(); // A (later mixin wins)

// resolution order: class -> last mixin -> ... -> first mixin
11

泛型

泛型类

泛型允许类和方法在任何类型上操作同时保持类型安全。<T> 声明类型参数。同一个 Stack 类适用于 int、String 或任何类型而无需转型。Dart 泛型是具体化的(运行时可用类型信息),不同于 Java 的擦除。

dart
class Stack<T> {
  final List<T> _items = [];

  void push(T item) => _items.add(item);
  T pop() => _items.removeLast();
  bool get isEmpty => _items.isEmpty;
}

var s = Stack<int>();
s.push(1);
s.push(2);
print(s.pop()); // 2

var names = Stack<String>();
names.push('Al');

泛型方法

方法可有独立于类的类型参数。类型参数通常从参数推断。泛型方法提供类型安全的工具,如 firstOrDefault,其返回类型取决于输入类型。

dart
// generic method with its own type parameter
T firstOrDefault<T>(List<T> list, T defaultValue) {
  return list.isEmpty ? defaultValue : list.first;
}

print(firstOrDefault<int>([1, 2, 3], 0)); // 1
print(firstOrDefault([], 'none'));        // none (type inferred)

// type often inferred from arguments

泛型约束(extends)

用 extends 将类型参数约束到某父类型(上界)。约束让你在泛型内调用上界类型的方法。T extends num 确保 T 支持算术。无约束时 T 被视为 Object?,可用方法很少。

dart
class Comparable<T> {
  int compareTo(T other);
}

// constrain T to subtypes of Comparable<T>
T max<T extends Comparable<T>>(T a, T b) {
  return a.compareTo(b) >= 0 ? a : b;
}

// number sum constraint
num sum<T extends num>(List<T> nums) =>
    nums.fold(0, (a, b) => a + b);

print(sum([1, 2.5, 3])); // 6.5

泛型集合

Dart 集合是泛型的,类型信息在运行时保留(具体化)。is List<int> 有效因为泛型未被擦除。Dart 集合是协变的:List<int> 可传给需要 List<num> 的地方,方便但写入时可能运行时报错。

dart
List<int> nums = [1, 2, 3];
Map<String, int> scores = {'a': 1, 'b': 2};
Set<double> uniq = {1.1, 2.2, 1.1};

// runtime type checks work (reified generics)
print(nums is List<int>);    // true
print(nums is List<String>); // false
print(scores is Map);        // true

// generic variance
void process(List<num> list) => print(list);
process(nums); // OK: int is a num

泛型 typedef

typedef 可以是泛型的,为泛型函数类型或类类型创建可复用的类型别名。Transformer<T> 为函数 T->T 起别名。这对在库中声明回调契约非常有价值。泛型 typedef 让复杂类型签名更可读。

dart
// generic function type alias
typedef Transformer<T> = T Function(T input);

int doubler(int x) => x * 2;
String upper(String s) => s.toUpperCase();

Transformer<int> dt = doubler;
Transformer<String> ut = upper;
print(dt(5));  // 10
print(ut('hi')); // HI

// generic class alias
typedef IntList = List<int>;
IntList xs = [1, 2, 3];
12

集合(List/Set/Map)

List 操作

List 支持丰富的函数式操作:where(过滤)、map(转换)、fold/reduce(累积)、sort、reversed。这些返回 Iterable——调用 .toList() 物化。在 Iterable 上链式调用时操作是惰性的。List 是 Dart 主力有序集合。

dart
var nums = [3, 1, 2];
nums.sort();
print(nums);            // [1, 2, 3]
print(nums.reversed.toList()); // [3, 2, 1]
print(nums.indexOf(2)); // 1
print(nums.contains(3));// true
print(nums.where((n) => n > 1).toList()); // [2, 3]
print(nums.map((n) => n * 2).toList());   // [2, 4, 6]
print(nums.fold(0, (a, b) => a + b));     // 6

Set 操作

Set 提供数学集合运算:并、交、差。将 List 转 Set 去重,再用 toList() 转回 List。Set 是无序的唯一元素集合——适合成员测试和去重。

dart
var a = {1, 2, 3};
var b = {2, 3, 4};
print(a.union(b));        // {1, 2, 3, 4}
print(a.intersection(b)); // {2, 3}
print(a.difference(b));   // {1}

var dedup = [1, 1, 2, 3, 3].toSet();
print(dedup.toList());    // [1, 2, 3]
print(dedup.contains(2)); // true

Map 操作

Map 将 keys 和 values 暴露为 Iterable。update() 原地修改值;map() 将条目转换为新 Map。forEach 迭代键值对。Map 保持插入顺序。用 putIfAbsent 实现「不存在才设置」语义。

dart
var ages = {'Alice': 30, 'Bob': 25};
ages['Carol'] = 28;
print(ages.keys);    // (Alice, Bob, Carol)
print(ages.values);  // (30, 25, 28)
print(ages.length);  // 3

ages.update('Bob', (v) => v + 1);
print(ages['Bob']);  // 26

ages.remove('Alice');
ages.forEach((k, v) => print('$k=$v'));

var mapped = ages.map((k, v) => MapEntry(k, v + 100));

展开与空安全展开

展开运算符 ... 将集合元素展开到另一个集合。...? 是空感知展开——优雅处理 null(展开为空)。展开适用于 List、Set 和 Map,使集合组合更简洁。

dart
var a = [1, 2];
var b = [0, ...a, 3];      // [0, 1, 2, 3]
print(b);

List<int>? maybe;
var c = [0, ...?maybe, 4]; // [0, 4] (null-spread is safe)
print(c);

var m1 = {'a': 1};
var m2 = {'b': 2, ...m1};  // {b: 2, a: 1}
print(m2);

collection-if 与 collection-for

collection-if 和 collection-for 是 Dart 特有语法,内联地按条件或迭代构建集合。避免单独的 add() 调用和中间变量。可组合和嵌套,产生简洁的声明式集合构造。

dart
var promo = true;
var menu = [
  'home',
  'products',
  if (promo) 'sale',
  'about',
];
print(menu); // [home, products, sale, about]

var nums = [1, 2, 3];
var doubled = [
  for (var n in nums) n * 2,
];
print(doubled); // [2, 4, 6]

// combine: [for (var x in xs) if (x > 0) x]

高阶方法

List/Iterable 支持 any、every、firstWhere、reduce、fold、skip、take、expand 等。fold 很强大——可携带任意类型的累加器。这些高阶方法支持无显式循环的声明式函数风格数据处理。

dart
var nums = [1, 2, 3, 4, 5];
print(nums.any((n) => n > 4));   // true
print(nums.every((n) => n > 0)); // true
print(nums.firstWhere((n) => n > 2)); // 3
print(nums.reduce((a, b) => a + b));  // 15
var byParity = nums.fold(<bool, List<int>>{}, (m, n) {
  m[n.isOdd] = [...?m[n.isOdd], n]; return m;
});
print(byParity); // {true: [1,3,5], false: [2,4]}
13

异步(Future / async / await)

Future 基础

Future 表示将在未来某个时刻可用的值(或错误)。Future.delayed 在一段时长后完成。用 .then() 注册 Future 完成时的回调。Future 是 Dart 异步模型的基础。

dart
Future<String> fetchUser() {
  return Future.delayed(Duration(seconds: 1), () => 'Alice');
}

void main() {
  fetchUser().then((name) {
    print('Got: $name'); // Got: Alice (after 1s)
  });
  print('waiting...');
}

async 与 await

标记函数 async 以在其中使用 await。async 函数总是返回 Future。await 暂停执行直到所等待的 Future 完成,然后返回其值——不阻塞事件循环。这让异步代码读起来像同步代码。

dart
Future<String> fetchUser() async {
  await Future.delayed(Duration(seconds: 1));
  return 'Alice';
}

Future<void> main() async {
  print('start');
  String name = await fetchUser();
  print('Got: $name');
  print('done');
}

异步错误处理

async 函数中抛出的错误以失败的 Future 传播。在 await 周围用 try/catch/finally 处理——与同步错误处理相同。catch 捕获错误对象;用 on ExceptionType 捕获特定类型。

dart
Future<int> divide(int a, int b) async {
  if (b == 0) throw Exception('divide by zero');
  return a ~/ b;
}

Future<void> main() async {
  try {
    var result = await divide(10, 0);
    print(result);
  } catch (e) {
    print('Error: $e'); // Error: Exception: divide by zero
  } finally {
    print('done');
  }
}

Future.then 与 catchError

不用 await 时,可用 .then()、.catchError()、.whenComplete() 链式 Future。每个 .then() 返回新 Future,支持管道。catchError 处理链中任何失败。可读性优先用 async/await;简单场景用链式。

dart
Future<int> compute() async => 42;

compute()
    .then((v) => v * 2)
    .then((v) => print(v))      // 84
    .catchError((e) => print('err: $e'))
    .whenComplete(() => print('cleanup'));

// chaining transforms the result type
Future<String> fetch() async => 'data';
fetch().then((s) => s.length).then(print); // 4

Future.wait 与 Future.any

Future.wait 并行运行多个 Future,全部完成时完成(返回结果 List)。Future.any 以第一个完成的 Future 的结果完成。wait 用于并行,any 用于竞速(如请求 + 超时)。

dart
Future<int> task(int n) async {
  await Future.delayed(Duration(milliseconds: n));
  return n;
}

// run in parallel, wait for all
var all = await Future.wait([task(100), task(50), task(200)]);
print(all); // [100, 50, 200]

// resolve with the first to complete
var first = await Future.any([task(100), task(50), task(200)]);
print(first); // 50 (fastest)

Completer

Completer 让你手动创建并完成 Future。调用 complete(value) 或 completeError(error) 完成。适用于将回调式 API 包装为 Future,或 Future 完成由你控制的外部事件触发时。

dart
import 'dart:async';

Completer<String> completer = Completer<String>();

// complete the future from elsewhere
Future<String> get value => completer.future;

completer.complete('resolved!');

void main() async {
  print(await value); // resolved!
}

// useful when bridging callback-based APIs to Futures
14

Stream

创建 Stream(async*)

async* 生成器函数返回 Stream 并用 yield 发射值。每次 yield 向流发射一个值。生成器是惰性的——值在被消费时才产生。async* 是 sync*(Iterable)的异步对应。

dart
Stream<int> countDown(int from) async* {
  while (from > 0) {
    await Future.delayed(Duration(seconds: 1));
    yield from;
    from--;
  }
}

void main() async {
  await for (var n in countDown(3)) {
    print(n); // 3, 2, 1 (one per second)
  }
}

监听 Stream

listen() 订阅 Stream 并注册数据、错误和完成的回调。它返回 StreamSubscription,可暂停、恢复或取消。单订阅流只允许一个监听器;广播流允许多个。

dart
var sub = countDown(3).listen(
  (n) => print('got $n'),
  onDone: () => print('done'),
  onError: (e) => print('err: $e'),
);

// pause/resume/cancel
sub.pause();
sub.resume();
// sub.cancel(); // stop listening

Stream<int> countDown(int from) async* {
  while (from > 0) yield from--;
}

Stream 方法

Stream 支持 where、map、take、skip、expand 等函数式转换,返回新 Stream。也可用 first、last、length、isEmpty 作为 Future。这些方法使 Stream 处理声明式——类似 List 但异步。

dart
var stream = Stream.fromIterable([1, 2, 3, 4]);

// transform like an Iterable
var evens = stream.where((n) => n.isEven);
var doubled = stream.map((n) => n * 2);

await for (var n in Stream.fromIterable([1,2,3]).map((n) => n * 10)) {
  print(n); // 10, 20, 30
}

print(await stream.first);  // 1
print(await stream.last);   // 4
print(await stream.length); // 4

StreamController

StreamController 提供一个 sink 手动向 Stream 推送数据、错误和完成事件。用于从非流源创建自定义流。默认是单订阅;传 broadcast: true 创建多监听器广播流。

dart
import 'dart:async';

var controller = StreamController<int>();

// add events manually
controller.add(1);
controller.add(2);
controller.addError('oops');
controller.close();

controller.stream.listen(
  (n) => print(n),           // 1, 2
  onError: (e) => print(e),  // oops
  onDone: () => print('done'),
);

// use controller.addError/sink.add for errors

await for 与广播流

await for 异步迭代 Stream,在事件到达时处理,直到流关闭。广播流(用 .broadcast() 或 StreamController.broadcast 创建)允许多个同时监听器,单订阅流只允许一个。

dart
// await for consumes a stream like a loop
Future<int> sumStream(Stream<int> s) async {
  var total = 0;
  await for (var n in s) {
    total += n;
  }
  return total;
}

print(await sumStream(Stream.fromIterable([1, 2, 3]))); // 6

// broadcast stream: multiple listeners
var bc = StreamController<int>.broadcast();
bc.stream.listen(print);
bc.stream.listen((n) => print('got $n'));
bc.add(5); // both listeners receive 5
15

异常处理

throw

throw 抛出异常,中断正常执行。Dart 可抛出任何非空对象,但习惯上抛出 Exception 或 Error 子类型。常见内置类型:ArgumentError、StateError、FormatException、RangeError。不鼓励抛出字符串。

dart
void checkAge(int age) {
  if (age < 0) {
    throw ArgumentError('age cannot be negative');
  }
  if (age > 150) {
    throw StateError('unrealistic age: $age');
  }
}

// you can throw any non-null object
void fail() => throw 'something went wrong';

try-catch-finally

用 on Type catch (e) 捕获特定异常类型,或 catch (e, stackTrace) 捕获任意异常并访问堆栈跟踪。finally 无论是否发生异常都执行。顺序重要:特定 on 子句放在通用 catch 之前。

dart
try {
  checkAge(-5);
} on ArgumentError catch (e) {
  print('argument error: $e');
} on StateError catch (e) {
  print('state error: $e');
} catch (e, stackTrace) {
  print('unknown: $e');
  print(stackTrace);
} finally {
  print('always runs');
}

自定义异常

通过实现 Exception 接口创建自定义异常。提供描述性消息并重写 toString() 以便可读。自定义异常使错误处理更精确——调用者可捕获特定失败模式。约定:命名为 *Exception。

dart
class InvalidCredentialsException implements Exception {
  final String message;
  InvalidCredentialsException(this.message);

  @override
  String toString() => 'InvalidCredentialsException: $message';
}

void login(String user, String pass) {
  if (user.isEmpty) {
    throw InvalidCredentialsException('username required');
  }
}

try {
  login('', 'x');
} on InvalidCredentialsException catch (e) {
  print(e); // InvalidCredentialsException: username required
}

rethrow

rethrow 重新抛出当前捕获的异常,同时保留原始堆栈跟踪。在记录日志或清理但未完全处理错误的中间件/包装函数中使用。与 throw e 不同,rethrow 保留原始堆栈跟踪以便调试。

dart
Future<void> logErrors(Future<void> Function() action) async {
  try {
    await action();
  } catch (e) {
    print('logging error: $e');
    rethrow; // re-throw the caught exception
  }
}

void main() async {
  try {
    await logErrors(() async => throw Exception('fail'));
  } catch (e) {
    print('handled upstream: $e');
  }
}

Error 与 Exception 对比

Exception 用于程序可合理捕获并恢复的运行时条件(如网络失败、错误输入)。Error 表示编程错误(类型错误、断言失败、索引越界),应在代码中修复而非运行时捕获。不鼓励捕获 Error。

dart
// Exception: recoverable, expected to be caught
class MyException implements Exception {}

// Error: programming bugs, not meant to be caught
class MyError extends Error {
  @override
  String toString() => 'MyError: invalid state';
}

void risky() {
  throw MyError();   // bug: should fix the code
  throw MyException(); // runtime condition: catch it
}

// assert failures, type errors, range errors are Errors
16

类型定义(Typedef)

函数 typedef

typedef 为函数类型创建别名,使签名可读且可复用。IntOperator 现在命名了 (int,int)->int 函数类型。没有 typedef 就要到处重复 int Function(int, int)。可将兼容的函数赋给 typedef 类型的变量。

dart
// alias for a function type
typedef IntOperator = int Function(int, int);

int add(int a, int b) => a + b;
int mul(int a, int b) => a * b;

IntOperator op = add;
print(op(2, 3)); // 5
op = mul;
print(op(2, 3)); // 6

// pass as a parameter
void apply(IntOperator f, int a, int b) => print(f(a, b));

泛型 typedef

typedef 可以是泛型的,带自己的类型参数。Mapper<T, R> 为函数 T->R 起别名。这让你一次表达丰富的函数契约并复用。泛型 typedef 对 Dart 中的类型化函数式编程模式至关重要。

dart
// generic function type alias
typedef Mapper<T, R> = R Function(T input);

String stringify(int n) => n.toString();
int lenOf(String s) => s.length;

Mapper<int, String> intToStr = stringify;
Mapper<String, int> strToLen = lenOf;

print(intToStr(42));  // '42'
print(strToLen('hi')); // 2

新式 typedef(非函数)

自 Dart 2.13 起,typedef 可为任何类型起别名——包括类,不仅是函数。这提高了复杂泛型类型的可读性。Dart 3 记录类型也可被起别名。别名与原类型完全可互换——运行时无差异。

dart
// Dart 2.13+: alias for any type, not just functions
typedef IntList = List<int>;
typedef StringMap<V> = Map<String, V>;

IntList nums = [1, 2, 3];
StringMap<int> scores = {'a': 1};

// alias for a record type (Dart 3)
typedef Point = ({double x, double y});
Point p = (x: 1.0, y: 2.0);
print(p.x); // 1.0

在 API 中使用 typedef

typedef 在 API 签名中大放异彩:它们清晰地命名回调契约。Predicate<T> 比 bool Function(T) 更易读。库作者用它们暴露干净、有文档的函数类型契约。调用者可传递任何兼容的函数或 lambda。

dart
typedef Predicate<T> = bool Function(T);

bool isEven(int n) => n.isEven;

List<T> filter<T>(List<T> list, Predicate<T> test) {
  return list.where(test).toList();
}

print(filter([1, 2, 3, 4], isEven)); // [2, 4]
print(filter(['', 'a', ''], (s) => s.isNotEmpty)); // [a]

// typedef makes callback contracts explicit

typedef 与内联函数类型对比

typedef 只是别名——在运行时和类型检查上与内联函数类型完全相同。typedef 提高可读性并集中契约,使修改在一处完成。对在多处使用的函数类型优先用 typedef。

dart
// these two are equivalent
typedef Handler = void Function(String event);

class EventBus {
  // using typedef
  void on(Handler handler) {}
  // using inline type
  void on2(void Function(String) handler) {}
}

// both accept the same functions
void myHandler(String e) => print(e);
EventBus().on(myHandler);
EventBus().on2(myHandler);
17

枚举

基本枚举

枚举声明一组固定的命名常量。每个值有 name(String)和 index(int)。values 按声明顺序返回所有枚举值列表。枚举隐式是 static 和 const。枚举的 switch 受益于穷尽性检查。

dart
enum Color { red, green, blue }

var c = Color.red;
print(c);           // Color.red
print(c.name);      // 'red'
print(c.index);     // 0
print(Color.values); // [Color.red, Color.green, Color.blue]
print(Color.green.index); // 1

增强枚举(Dart 3)

Dart 3 增强枚举可有字段、构造函数、方法和 getter——像类一样。构造函数必须是 const。这让枚举携带数据和行为,取代许多类层次结构的用法。前几个值仍自动获得 name 和 index 属性。

dart
enum Vehicle {
  car('Car', 4),
  bike('Bike', 2),
  truck('Truck', 6);

  final String label;
  final int wheels;
  const Vehicle(this.label, this.wheels);

  int get axles => wheels ~/ 2;
}

print(Vehicle.car.label);   // Car
print(Vehicle.bike.wheels); // 2
print(Vehicle.truck.axles); // 3

遍历与 switch

枚举值可通过 .values 迭代。对枚举 switch 是穷尽的——如果覆盖所有 case 则不需要 default(Dart 3 对密封类型和枚举强制)。这让新增枚举值成为编译期信号,提醒更新每个 switch。

dart
enum Status { pending, active, done }

// iterate all values
for (var s in Status.values) {
  print(s.name);
}

// exhaustive switch (no default needed)
String label(Status s) => switch (s) {
  Status.pending => 'Waiting',
  Status.active => 'Running',
  Status.done => 'Finished',
};

print(label(Status.active)); // Running

带方法的枚举

增强枚举可定义使用 this 引用当前值的方法和 getter。枚举方法内对 this 的 switch 无需 default 即穷尽。这种模式干净地封装每个枚举值的行为,常取代工具类。

dart
enum HttpStatus {
  ok(200),
  notFound(404),
  serverError(500);

  final int code;
  const HttpStatus(this.code);

  bool get isSuccess => code >= 200 && code < 300;
  String get reason => switch (this) {
    ok => 'OK',
    notFound => 'Not Found',
    serverError => 'Internal Server Error',
  };
}

print(HttpStatus.ok.isSuccess);   // true
print(HttpStatus.notFound.reason); // Not Found

枚举比较

每个枚举值是单例——每个值每个程序只有一个实例。== 实际上按身份比较。index 允许按声明位置排序。用 == 比较相等;identical() 也可用因为值被规范化。枚举是很好的 Map 键和 Set 元素。

dart
enum Priority { low, medium, high }

var a = Priority.low;
var b = Priority.high;

print(a == b);           // false
print(a == Priority.low);// true
print(a.index < b.index);// true
print(identical(a, Priority.low)); // true

// enums are singletons: only one instance per value
// use == for equality, not identical (though both work)
18

注解(元数据)

内置注解

注解以 @ 开头,为库、类、字段、参数和方法附加元数据。@override 标记方法覆盖(编译期检查)。@deprecated/@Deprecated 标记过时 API。@protected、@visibleForTesting、@visibleForOverriding 控制可见性提示。

dart
class Animal {
  @override
  String toString() => 'Animal';

  @Deprecated('use newName instead')
  String oldName = 'x';
  String newName = 'x';

  @protected
  void internalMethod() {}

  @visibleForTesting
  String testHook() => 'test';
}

@override 与 @Deprecated

@override 告诉编译器验证方法确实覆盖父类成员——在编译期捕获拼写错误。@Deprecated('message') 标记 API 过时;分析器在每个调用点用提供的消息发出警告,引导迁移到替代方案。

dart
class Base {
  void greet() {}
  String name = 'base';
}

class Derived extends Base {
  @override
  void greet() => print('hi');

  // @override verifies the parent method exists
  // typo here would be a compile error:
  // @override void greeet() {}
}

// @Deprecated emits a warning at the call site
@Deprecated('use bar()')
void foo() {}
void bar() {}

自定义注解

任何带 const 构造函数的类都可用作注解。定义自定义注解为工具、文档或代码生成标记代码。读取注解需要 dart:mirrors(仅 VM,Flutter 中禁用)或 build_runner 代码生成。常见于 JSON 序列化包。

dart
// a custom annotation is just a const constructor class
class Todo {
  final String msg;
  const Todo(this.msg);
}

class Service {
  @Todo('refactor to use cache')
  void fetchData() {}

  @Todo('add tests')
  void process() {}
}

// annotations are accessed via dart:mirrors (VM) or
// code generation (build_runner) in practice

@immutable 与 @JsonSerializable

@immutable(来自 package:meta)标记构造后不应改变的类;子类和字段应为 final。@JsonSerializable(来自 json_serializable)通过 build_runner 触发 JSON 转换的代码生成。注解驱动许多 Dart/Flutter 生态。

dart
import 'package:meta/meta.dart';
// requires the 'meta' package

@immutable
class User {
  final String name;
  final int age;
  const User(this.name, this.age);
}

// with package:json_annotation / json_serializable
// @JsonSerializable()
// class Product {
//   final String id;
//   Product(this.id);
//   factory Product.fromJson(Map<String, dynamic> j) => ...;
// }

参数上的注解

注解也可放在参数、库声明和 typedef 上。空安全前 @required 标记必填命名参数;现代等价物是 required 关键字。参数上的注解被序列化和 DI 框架广泛使用。

dart
class Required {
  const Required([this.reason]);
  final String? reason;
}

class Service {
  // annotation on a parameter
  void create({
    @Required('name is mandatory') String? name,
    @protected int? internal,
  }) {
    print(name);
  }
}

// @required is built into Dart (the 'required' keyword
// is preferred in Dart 2.12+ for null safety)
19

库与可见性

import 与 show/hide

import 将库的公共 API 引入作用域。用 show 只导入特定名称,hide 排除名称,as 给前缀(避免冲突)。dart: 是 SDK 库;package: 是 pub 包;相对路径用于本地文件。

dart
// import an entire library
import 'dart:io';
import 'package:http/http.dart';

// import only specific names
import 'dart:math' show Random, pi;

// hide specific names
import 'dart:async' hide Timer;

// prefix to avoid name clashes
import 'package:http/http.dart' as http;
http.get(Uri.parse('https://example.com'));

export 与 part

part 将单个库拆分到多个文件(它们共享库的私有命名空间)。export 重新导出另一个库的符号,使导入你库的人也能获得——用于创建单一公共入口。多数模块化设计优先用 export 而非 part。

dart
// library.dart
library my_lib;

// split implementation across files
part 'src/widget_a.dart';
part 'src/widget_b.dart';

// re-export another library's API
export 'src/utils.dart' show formatDate, parseDate;

// users import library.dart and get everything

可见性(_下划线)

Dart 的可见性是基于库而非类的:以 _ 开头的标识符对声明它们的库(文件或 part-of 组)私有。同一库可访问其中定义的类的私有成员。没有 protected 或包私有关键字。

dart
// names starting with _ are library-private
class _Internal {
  void _helper() {}
}

class Public {
  String _secret = 'hidden';   // private field
  String name = 'visible';     // public field

  String _process() => 'internal';
  String reveal() => _process();
}

// _secret is accessible anywhere in the SAME library/file
// but not from other libraries that import this file

延迟(惰性)加载

延迟加载(惰性 import)只在首次调用 loadLibrary() 时下载库——在 web 上可减少初始包大小。延迟库的符号通过前缀访问。web 支持;原生平台急切加载。适合很少使用的功能。

dart
// load a library on demand (web only)
import 'package:heavy_lib/heavy.dart' deferred as heavy;

Future<void> main() async {
  // library is NOT loaded until this call
  await heavy.loadLibrary();
  heavy.SomeClass().doWork();
}

// useful for splitting large web bundles and
// loading rarely-used features only when needed

library 指令

library 指令为库命名,在现代 Dart 中是可选的。主要在使用 part/part-of 将库拆分到多文件时相关。多数 Dart 文件省略该指令,被视为匿名库。名称有助于工具和文档。

dart
// explicit library declaration
library my_package.utils;

import 'dart:math';

part 'src/helper.dart';

const double version = 1.0;

// a 'library' name is optional in modern Dart;
// it's mainly used with part/part-of and tooling
// most files omit it and are treated as anonymous libraries

part 与 part of

part/part-of 将一个库拆分到多个文件:主文件声明 part 'file.dart';部分文件声明 'part of library;'。部分共享库作用域包括私有(_name)成员。新代码优先用独立库加 export——part 用于紧密耦合的实现。

dart
// shapes.dart
library shapes;
part 'circle.dart';
part 'square.dart';

class Shape {}

// circle.dart
part of shapes;
class Circle extends Shape {}

// files in the same library share private members
// (_name visible across all parts)
20

空安全

可空与非空类型

健全的空安全:int 永远不能为 null;int? 可以。编译器强制执行此规则,在编译期捕获 NullPointerException。使用前必须初始化非空变量。在任意类型后加 ? 使其可空。这是 Dart 自 2.12 起的核心安全特性。

dart
int a = 42;          // non-nullable: cannot be null
int? b;              // nullable: can be null
print(b);            // null
b = 10;
print(b);            // 10

String name = 'Al';  // non-nullable
String? middle;      // nullable

// non-nullable types are guaranteed non-null
// int x = null;      // ERROR
// print(a.length);   // safe: a is non-null

空断言(!)

!(空断言)运算符告诉编译器「我知道这是非空的;相信我」。如果值实际为 null 则运行时抛出。慎用——优先用 ?. 或空检查。过度使用 ! 会破坏空安全。当框架保证值已设置时合理使用。

dart
String? maybeName;
// int len = maybeName.length; // ERROR: maybeName is nullable

maybeName = 'Alice';
int len = maybeName!.length;  // ! asserts non-null
print(len); // 5

// throws if null at runtime:
// String? n; print(n!.length); // NoSuchMethodError/null

空感知运算符

?. 是空感知访问(目标为 null 时返回 null)。?? 是空合并运算符(提供默认值)。??= 仅在当前值为 null 时赋值。这些让你无需显式 if-null 检查即可优雅处理可空值。

dart
String? name;
print(name?.length);     // null (safe access)
print(name?.length ?? 0);// 0 (default if null)

name = 'Alice';
print(name?.length);     // 5

name ??= 'Bob';          // assign only if null
print(name);             // Alice (already set)

List<int>? list;
print(list?.first);      // null
print(list?.first ?? -1);// -1

类型提升

类型提升:在空检查(x != null)或类型检查(x is String)后,编译器在该分支内收窄类型——无需显式转型。非空赋值后也会提升。局部变量提升良好;字段可能需要显式局部副本。

dart
String? name;
if (name != null) {
  // name is promoted to non-nullable String here
  print(name.length); // safe, no ! needed
}

// promoted via is check
Object obj = 'hello';
if (obj is String) {
  print(obj.length); // smart-cast to String
}

// promoted via assignment
int? x;
x = 5;
print(x.abs()); // x is non-null after assignment

late 与 required

late 标记在声明后、首次使用前初始化的非空变量——延迟初始化。late final 初始化一次(若有初始化器则惰性)。required 标记必填命名参数。它们与空安全干净地集成。

dart
class Config {
  // late: non-nullable, initialized later
  late final String value = _load();

  // late without initializer: assign before first use
  late final int computed;
  Config() {
    computed = expensive();
  }

  String _load() => 'loaded';
  int expensive() => 42;
}

// required: mandatory named parameter
void build({required String name}) {}
build(name: 'Al'); // OK
// build(); // ERROR: missing required

late 惰性初始化

带初始化器的 late 字段是惰性的——初始化器在首次访问时运行,而非构造时。结果被缓存供后续访问。非常适合昂贵初始化、循环引用和依赖 this 完全构造的字段。late final 使其成为一次性计算。

dart
class Service {
  // lazy: _expensive runs only on first access
  late final int cache = _expensive();

  int _expensive() {
    print('computing...');
    return 42;
  }
}

var s = Service();
print('created');
print(s.cache); // computing... 42
print(s.cache); // 42 (cached, no recompute)

// late fields with initializers are evaluated lazily

这篇内容对您有帮助吗?