跳到主要内容

Constructors(构造函数)

构造函数负责创建并初始化类的实例。Dart 的构造函数与类同名、没有返回类型;除了普通的生成式构造函数,还支持命名构造函数、常量构造函数、重定向构造函数和工厂构造函数。

生成式构造函数

生成式构造函数(generative constructor)每次调用都会创建一个新实例。初始化形式参数 this.name 可以直接初始化同名字段:

class User {
final String name;
int loginCount;

User(this.name, {this.loginCount = 0});
}

void main() {
final user = User('Alice', loginCount: 2);
print('${user.name}: ${user.loginCount}'); // Alice: 2
}

如果类没有声明任何构造函数,Dart 会提供一个无参数、无名称的默认构造函数。只要显式声明了任意构造函数,这个默认构造函数就不再存在。

命名构造函数

Dart 不按参数列表重载构造函数。需要表达多种创建方式时,可以使用 ClassName.name 声明命名构造函数:

class Point {
final double x;
final double y;

Point(this.x, this.y);

Point.origin()
: x = 0,
y = 0;

Point.fromJson(Map<String, Object?> json)
: x = json['x'] as double,
y = json['y'] as double;
}

调用时必须写出名称,例如 Point.origin()。命名构造函数不会自动继承给子类。

初始化列表

构造函数体执行前,可以在冒号后的初始化列表中初始化字段、执行断言,或调用超类构造函数:

class TemperatureRange {
final double min;
final double max;

TemperatureRange(double first, double second)
: assert(first <= second),
min = first,
max = second;
}

初始化列表右侧不能使用 this,因为此时对象还没有完成初始化。初始化顺序可以概括为:先执行初始化列表,再调用超类构造函数,最后执行当前构造函数体。

重定向构造函数

同一个类中的构造函数可以把初始化工作交给另一个构造函数:

class Size {
final double width;
final double height;

Size(this.width, this.height);

Size.square(double side) : this(side, side);
}

重定向构造函数不能有函数体,因为真正的初始化由目标构造函数完成。

常量构造函数

所有实例字段均为 final 时,可以声明 const 构造函数,让调用方创建编译时常量:

class Color {
final int red;
final int green;
final int blue;

const Color(this.red, this.green, this.blue);
}

void main() {
const first = Color(255, 0, 0);
const second = Color(255, 0, 0);

print(identical(first, second)); // true
}

const 构造函数只是允许创建常量;调用本身还必须处于常量上下文,或显式带有 const。相同参数的常量实例会被规范化为同一个对象。

工厂构造函数

factory 构造函数不保证创建新实例,也可以返回当前类的子类型。它适合缓存实例、创建前校验,或隐藏具体实现:

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

final String name;

Logger._internal(this.name);

factory Logger(String name) {
return _cache.putIfAbsent(name, () => Logger._internal(name));
}
}

void main() {
final first = Logger('network');
final second = Logger('network');

print(identical(first, second)); // true
}

工厂构造函数没有正在初始化的 this,因此不能像生成式构造函数那样直接访问实例成员。

超类构造函数与 super 参数

子类构造函数可以在初始化列表中调用超类的命名或无名构造函数。只需原样转发参数时,可以使用 super 参数:

class Person {
final String name;

Person(this.name);
}

class Employee extends Person {
final int id;

Employee(this.id, super.name);
}

如果没有显式调用,Dart 会尝试调用超类无参数、无名称的构造函数。该构造函数不存在时,子类必须明确选择一个可用的超类构造函数。

构造函数 tear-off

省略调用括号可以取得构造函数 tear-off,也就是与该构造函数签名相同的函数对象:

class Point {
final int x;
final int y;

Point(this.x, this.y);
}

void main() {
final createPoint = Point.new;
final points = [(1, 2), (3, 4)]
.map((coordinates) => createPoint(coordinates.$1, coordinates.$2))
.toList();

print(points.length); // 2
}

无名构造函数的 tear-off 写作 ClassName.new,命名构造函数写作 ClassName.named

TypeScript 对比:TypeScript 只有一个 constructor 实现,通常通过静态工厂方法模拟命名或工厂构造函数。Dart 把这些创建方式直接纳入构造函数语法,并支持 const 实例和构造函数 tear-off。

常见误区

  • 构造函数没有返回类型,连 void 也不能写。
  • 子类不会继承父类的构造函数,需要自行声明并调用 super
  • factory 可能返回已有实例,调用方不应假设每次都会得到新对象。
  • 初始化列表在构造函数体之前执行,不能在其中读取尚未完成初始化的 this

小结

  • 生成式构造函数创建新实例,初始化形式参数用于直接初始化字段。
  • 命名构造函数表达不同创建意图,重定向构造函数复用同类构造逻辑。
  • const 构造函数支持编译时常量,factory 构造函数可以缓存或返回具体实现。
  • 子类需要显式处理超类构造过程,简单转发可使用 super 参数。
  • 构造函数 tear-off 可以把对象创建逻辑当作函数值传递。