构造函数
构造函数通过创建一个与其类同名的函数来声明 (额外还可以附加一个可选标识符,如 命名构造函数 所述)。通过生成构造函数, 创建一个类的实例:
class Point {
num x, y;
Point(num x, num y) {
// 还有更好的方式来实现下面代码,敬请关注。
this.x = x;
this.y = y;
}
}
当前实例使用 this 关键字引用。
提示: this 关键字在存在命名冲突时使用 。 否则this按照 Dart 风格应该省略。
构造函数传入的参数的值通常会赋值给对应的实例变量, Dart 自身的语法糖 精简了这些代码:
class Point {
num x, y;
// 在构造函数体执行前,
// 语法糖已经设置了变量 x 和 y。
Point(this.x, this.y);
}
默认构造函数
Dart 会在没有声明构造函数的情况下提供一个默认的构造函数。 默认构造函数没有参数还会调用父类的无参构造函数。
构造函数不被继承
子类不会继承父类的构造函数。 如果子类不声明构造函数,那么它就只有默认构造函数 (匿名,没有参数) 。
命名构造函数
为一个类实现多个构造函数可以用命名构造函数, 使用命名构造函数可以更清晰的表明函数意图:
class Point {
num x, y;
Point(this.x, this.y);
// 命名构造函数
Point.origin() {
x = 0;
y = 0;
}
}
构造函数不能被继承,也就是说子类不会继承父类的命名构造函数。 若用父类中定义的命名构造函数创建子类,就必须在子类中实现该构造函数。
调用父类非默认构造函数
子类的构造函数默认情况下会自动调用父类的默认构造函数(匿名,无参数)。 父类的构造函数在子类构造函数体开始执行的位置被调用。 如果提供了一个 initializer list (初始化参数列表), 则初始化参数列表在父类构造函数执行之前执行。 总之,执行顺序如下:
1.initializer list (初始化参数列表)
2.superclass’s no-arg constructor (父类的无名构造函数)
3.main class’s no-arg constructor (主类的无名构造函数)
如果父类中构造函数没有匿名无参的, 则需要手工调用父类的其他构造函数。 在当前构造函数冒号 (:) 之后,函数体之前,声明调用父类构造函数。
下例中:父类 Person 的命名构造函数由Employee 类的构造函数调用了.
class Person {
String firstName;
Person.fromJson(Map data) {
print('in Person');
}
}
class Employee extends Person {
// Person does not have a default constructor;
// you must call super.fromJson(data).
Employee.fromJson(Map data) : super.fromJson(data) {
print('in Employee');
}
}
main() {
var emp = new Employee.fromJson({});
// Prints:
// in Person
// in Employee
if (emp is Person) {
// Type check
emp.firstName = 'Bob';
}
(emp as Person).firstName = 'Bob';
}
因为在构造函数执行之前执行父类的构造函数参数, 所以参数可以是一个表达式或者一个方法调用:
class Employee extends Person {
Employee() : super.fromJson(getDefaultData());
// ···
}
在调用父类构造函数的参数无法访问 this 。 例如,参数可以为静态函数但是不能是实例函数。
初始化列表
除了调用超类构造函数之外, 还可以在构造函数体执行之前初始化实例变量。 各参数的初始化用逗号分隔。
// 在构造函数体执行之前,
// 通过初始列表设置实例变量。
Point.fromJson(Map<String, num> json)
: x = json['x'],
y = json['y'] {
print('In Point.fromJson(): ($x, $y)');
}
警告: 初始化程序的右侧无法访问 this 。
使用 assert 在开发期间来验证输入的初始化列表。
Point.withAssert(this.x, this.y) : assert(x >= 0) {
print('In Point.withAssert(): ($x, $y)');
}
final 字段使用初始化列表可以很方便的设置 。 下面示例演示了,如何使用初始化列表初始化设置三个 final 字段。
import 'dart:math';
class Point {
final num x;
final num y;
final num distanceFromOrigin;
Point(x, y)
: x = x,
y = y,
distanceFromOrigin = sqrt(x * x + y * y);
}
main() {
var p = new Point(2, 3);
print(p.distanceFromOrigin);
}
重定向构造函数
有时重定向到同一个类中的另一个构造函数是构造函数的唯一目的。 重定向构造函数的函数体为空, 构造函数的调用在冒号 (:) 之后。
class Point {
num x, y;
// 类的主构造函数。
Point(this.x, this.y);
// 指向主构造函数
Point.alongXAxis(num x) : this(x, 0);
}
常量构造函数
如果该类生成的对象是固定不变的, 那么就可以把这些对象定义为编译时常量。 为此,需要定义一个 const 构造函数, 并且声明所有实例变量为 final。
class ImmutablePoint {
static final ImmutablePoint origin =
const ImmutablePoint(0, 0);
final num x, y;
const ImmutablePoint(this.x, this.y);
}
创建常量构造函数的实例并不总是常量。
工厂构造函数
当构造函数执行时不只创建这个类的一个新实例,则使用 factory 关键字。 例如,一个工厂构造函数可能会返回一个 cache 中的实例, 或者可能返回一个子类的实例。
从缓存中返回对象的工厂构造函数 示例:
class Logger {
final String name;
bool mute = false;
// 从命名的 _ 可以知,
// _cache 是私有属性。
static final Map<String, Logger> _cache =
<String, Logger>{};
factory Logger(String name) {
if (_cache.containsKey(name)) {
return _cache[name];
} else {
final logger = Logger._internal(name);
_cache[name] = logger;
return logger;
}
}
Logger._internal(this.name);
void log(String msg) {
if (!mute) print(msg);
}
}
工厂构造函数无法访问 this。
工厂构造函的调用方式与其他构造函数一样:
var logger = Logger('UI');
logger.log('Button clicked');