序言
在dart中,允许多种形式的构造方法,上篇类中,也有涉略。在这篇文章中我们进行深入的学习。
构造 方法的类型
Generative constructors、Default constructors、Named constructors、Constant constructors、Redirecting constructors、Factory constructors、Redirecting factory constructors以及Constructor tear-offs
这些都是官方文档中给到的构造方法的类型,下面我们逐一了解
Generative constructors
是一种类似java的构造函数,基础的
dart
class Point {
// Instance variables to hold the coordinates of the point.
double x;
double y;
// Generative constructor with initializing formal parameters:
Point(this.x, this.y);
}
Default constructors
默认构造,当你没有明确声明构造方法时,就会使用这个
Named constructors
命名构造,当你给构造函数起了一个其他名字时,就是命名构造,但命名必须遵循class.name的形式,如下
dart
const double xOrigin = 0;
const double yOrigin = 0;
class Point {
final double x;
final double y;
// Sets the x and y instance variables
// before the constructor body runs.
Point(this.x, this.y);
// Named constructor
Point.origin() : x = xOrigin, y = yOrigin;
}
子类不会继承父类的命名构造

Constant constructors
如果类生成不变的对象 ,将这些对象设置为编译时常量。要使对象成为编译时常量,定义一个const构造函数 ,并将所有实例变量设置为final。
dart
class ImmutablePoint {
static const ImmutablePoint origin = ImmutablePoint(0, 0);
final double x, y;
const ImmutablePoint(this.x, this.y);
}
常量构造函数并不总是创建常量。它们可以在非常量上下文中调用。
如下
dart
void main() {
const ImmutablePoint p = ImmutablePoint(10, 20);
var p1 = const ImmutablePoint(10, 20);
var p2 = ImmutablePoint(10, 20);//这个叫非常量上下文
print(p == p1);//true
print(p == p2);//false
print(p1 == p2);//false
}
class ImmutablePoint {
static const ImmutablePoint origin = ImmutablePoint(0, 0);
final double x, y;
const ImmutablePoint(this.x, this.y);
}
Redirecting constructors
构造函数可以重定向到同一个类中的另一个构造函数。重定向构造函数有一个空主体。构造函数使用this代替冒号(:)后面的类名。
dart
class Point {
double x, y;
// The main constructor for this class.
Point(this.x, this.y);
// Delegates to the main constructor.
Point.alongXAxis(double x) : this(x, 0);
}
Factory constructors
这个上篇文章也讲到了
当遇到下列两种实现构造函数的情况之一时,请使用factory关键字:
构造函数并不总是创建类的新实例。尽管工厂构造函数不能返回null,但它可能返回:
从缓存中创建一个现有实例,而不是创建一个新的实例
子类型的新实例
在构建实例之前,你需要执行一些重要的工作。这可能包括检查参数或进行任何在初始化列表中无法处理的其他处理。
dart
var logger = Logger('UI');
logger.log('Button clicked');
var logMap = {'name': 'UI'};
var loggerJson = Logger.fromJson(logMap);
class Logger {
final String name;
bool mute = false;
// _cache is library-private, thanks to
// the _ in front of its name.
static final Map<String, Logger> _cache = <String, Logger>{};
factory Logger(String name) {
return _cache.putIfAbsent(name, () => Logger._internal(name));
}
factory Logger.fromJson(Map<String, Object> json) {
return Logger(json['name'].toString());
}
Logger._internal(this.name);
void log(String msg) {
if (!mute) print(msg);
}
}
工厂构造不能访问this
Redirecting factory constructors
有了上面的经验这个也不难理解了
dart
factory Listenable.merge(List<Listenable> listenables) = _MergingListenable
普通的工厂构造函数似乎可以创建和返回其他类的实例。这将使重定向工厂变得不必要。
重定向工厂有几个优点:
抽象类可能会提供一个使用另一个类的常量构造函数的常量构造函数。
重定向工厂构造函数避免了转发器重复形式参数及其默认值的需要。
Constructor tear-offs
支持撕开,看下面的例子
dart
final List<int> charCodes = [72, 101, 108, 108, 111, 33]; // "Hello!"
// Use a tear-off for a named constructor:
var strings = charCodes.map(String.fromCharCode);
// Use a tear-off for an unnamed constructor:
var buffers = charCodes.map(StringBuffer.new);


上面的代码中使用了lamada,去除后如下
dart
// Instead of a lambda for a named constructor:
var strings = charCodes.map((code) => String.fromCharCode(code));
// Instead of a lambda for an unnamed constructor:
var buffers = charCodes.map((code) => StringBuffer(code));
Instance variable initialization 实例变量初始化
dart中有3种变量初始化方式
Initialize instance variables in the declaration
声明时初始化
dart
class PointA {
double x = 1.0;
double y = 2.0;
// The implicit default constructor sets these variables to (1.0,2.0)
// PointA();
@override
String toString() {
return 'PointA($x,$y)';
}
}
Use initializing formal parameters
使用初始化形式参数,初始化参数也可以是可选的
dart
class PointB {
final double x;
final double y;
// Sets the x and y instance variables
// before the constructor body runs.
PointB(this.x, this.y);
// 初始化参数也可以是可选参数
PointB.optional([this.x = 0.0, this.y = 0.0]);
}
私有字段不能用作命名初始化形式变量。可以使用下面的方式
dart
class PointB {
// ...
PointB.namedPrivate({required double x, required double y})
: _x = x,
_y = y;
// ...
}
上面的写法也适用于命名变量
dart
class PointC {
double x; // must be set in constructor
double y; // must be set in constructor
// Generative constructor with initializing formal parameters
// with default values
PointC.named({this.x = 1.0, this.y = 1.0});
@override
String toString() {
return 'PointC.named($x,$y)';
}
}
// Constructor using named variables.
final pointC = PointC.named(x: 2.0, y: 2.0);
所有通过初始化形式参数引入的变量都是final变量,并且只在初始化变量的作用域内。
要执行无法在初始化列表中表达的逻辑,请使用该逻辑创建工厂构造函数或静态方法。然后,您可以将计算值传递给普通构造函数。
构造函数的参数可以声明为可空类型,并且可以被初始化或不被初始化
dart
class PointD {
double? x; // null if not set in constructor
double? y; // null if not set in constructor
// Generative constructor with initializing formal parameters
PointD(this.x, this.y);
@override
String toString() {
return 'PointD($x,$y)';
}
}
Use an initializer list
使用初始化列表
在运行构造函数体之前,可以初始化实例变量。用逗号分隔初始化方法。
dart
// Initializer list sets instance variables before
// the constructor body runs.
Point.fromJson(Map<String, double> json) : x = json['x']!, y = json['y']! {
print('In Point.fromJson(): ($x, $y)');
}
初始化列表的右侧不能访问this
在开发期间,可以通过初始话列表使用assert验证输入参数
dart
Point.withAssert(this.x, this.y) : assert(x >= 0) {
print('In Point.withAssert(): ($x, $y)');
}
初始化列表也可以用来设置final值
如下
dart
class A {
final int x;
// A(this.x);
A(int u) : x = u;
}
Constructor inheritance
构造函数的继承
子类不会从超类或直接父类继承构造方法。如果类没有声明构造方法,那么只能使用默认的构造方法。
类可以继承超类的参数。这些称为超参数(super parameter)
构造函数的工作方式与调用静态方法链的方式有些类似。每个子类都可以调用超类的构造函数来初始化实例,就像子类调用超类的静态方法一样。这个过程不会"继承"构造函数体或签名。
Non-default superclass constructors
非默认超类构造
Dart按以下顺序执行构造函数:
初始化器列表
超类的未命名、无参数的构造函数
主类的无参数构造函数
如果超类没有未命名的、无参数的构造方法,就调用超类中的一个构造方法。在构造函数主体之前(如果有的话),在冒号(:)之后指定超类的构造函数。
dart
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().
Employee.fromJson(Map data) : super.fromJson(data) {
print('in Employee');
}
}
由于Dart在调用构造函数之前计算超类构造函数的参数,因此参数可以是类似于函数调用的表达式
dart
class Employee extends Person {
Employee() : super.fromJson(fetchDefaultData());
// ···
}
超类构造函数的参数无法访问this。例如,参数可以调用静态方法,但不能调用实例方法
Super parameters
为了避免将每个参数都传入构造函数的父类调用中,可以使用 super-initializer 参数(超类初始化参数)将参数直接转发到指定或默认的父类构造函数。
此功能不能与重定向构造函数一起使用。
dart
class Dog extends Animal {
final String breed;
// 传统方式:需要将 name 手动传递给 super
Dog(String name, this.breed) : super(name);
}
dart
class Dog extends Animal {
final String breed;
// 新方式:使用 super-initializer 参数
Dog(super.name, this.breed);
// ↑ super.name 自动将 name 参数转发给父类 Animal 构造函数
}
下面是个错误案例
dart
Vector3d.xAxisError(super.x): z = 0, super(0); // BAD
这个命名构造函数试图设置x的值两次:一次是在super构造函数中,另一次是作为位置超参数。因为它们都指向位置参数x,所以会导致错误。
当超类构造函数有命名参数时,你可以将它们分配到:
命名的 super 参数(如下例中的 super.y)
超类构造函数调用中的命名参数(如 super.named(x: 0))
dart
class Vector2d {
// ...
Vector2d.named({required this.x, required this.y});
}
class Vector3d extends Vector2d {
final double z;
// Forward the y parameter to the named super constructor like:
// Vector3d.yzPlane({required double y, required this.z})
// : super.named(x: 0, y: y);
Vector3d.yzPlane({required super.y, required this.z}) : super.named(x: 0);
}
小结
太灵活了