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 可以把对象创建逻辑当作函数值传递。