构造函数
构造函数是创建类实例的特殊函数。
Dart 实现了许多类型的构造函数。除了默认构造函数外,这些函数使用与其类相同的名称。
- 生成构造函数:创建新实例并初始化实例变量。
- 默认构造函数:当未指定构造函数时,用于创建新实例。它不接受参数,也不命名。
- 命名构造函数:阐明构造函数的目的,或允许为同一个类创建多个构造函数。
- 常量构造函数:创建作为编译时常量的实例。
- 工厂构造函数:创建子类型的新的实例,或者从缓存返回现有的实例。
- 重定向构造函数:将调用转发到同一个类中的另一个构造函数。
构造函数类型
#生成构造函数
#要实例化一个类,请使用生成构造函数。
class Point {
// Initializer list of variables and values
double x = 2.0;
double y = 2.0;
// Generative constructor with initializing formal parameters:
Point(this.x, this.y);
}
默认构造函数
#如果你没有声明构造函数,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;
}
子类不会继承超类的命名构造函数。要创建具有在超类中定义的命名构造函数的子类,请在子类中实现该构造函数。
常量构造函数
#如果你的类生成不变的对象,请将这些对象作为编译时常量。要使对象成为编译时常量,请定义一个 const
构造函数,并将所有实例变量设置为 final
。
class ImmutablePoint {
static const ImmutablePoint origin = ImmutablePoint(0, 0);
final double x, y;
const ImmutablePoint(this.x, this.y);
}
常量构造函数并不总是创建常量。它们可能在非 const
上下文中被调用。要了解更多信息,请参阅有关 使用构造函数 的部分。
重定向构造函数
#构造函数可以重定向到同一个类中的另一个构造函数。重定向构造函数有一个空主体。构造函数在冒号 (:) 后使用 this
而不是类名。
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
关键字
构造函数并不总是创建其类的新的实例。虽然工厂构造函数不能返回
null
,但它可能会返回- 从缓存中返回现有的实例,而不是创建新的实例
- 子类型的新的实例
你需要在构造实例之前执行一些非平凡的工作。这可能包括检查参数或执行初始化列表中无法处理的任何其他处理。
以下示例包含两个工厂构造函数。
Logger
工厂构造函数从缓存返回对象。Logger.fromJson
工厂构造函数从 JSON 对象初始化一个final
变量。
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);
}
}
像任何其他构造函数一样使用工厂构造函数
var logger = Logger('UI');
logger.log('Button clicked');
var logMap = {'name': 'UI'};
var loggerJson = Logger.fromJson(logMap);
重定向工厂构造函数
#重定向工厂构造函数指定调用另一个类的构造函数,以便在有人调用重定向构造函数时使用。
factory Listenable.merge(List<Listenable> listenables) = _MergingListenable
看起来普通的工厂构造函数可以创建并返回其他类的实例。这将使重定向工厂变得不必要。重定向工厂有几个优点
- 抽象类可以提供一个常量构造函数,该构造函数使用另一个类的常量构造函数。
- 重定向工厂构造函数避免了转发器重复形式参数及其默认值的需要。
构造函数断言
#Dart 允许你在不调用构造函数的情况下将构造函数作为参数提供。称为断言(因为你断言了括号)作为一个闭包,它使用相同的参数调用构造函数。
如果断言是一个与方法接受的签名和返回类型相同的构造函数,则可以使用断言作为参数或变量。
断言与 lambda 或匿名函数不同。lambda 作为构造函数的包装器,而断言是构造函数。
使用断言
// 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);
不是 Lambda
// 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));
有关更多讨论,请观看此关于断言的 Decoding Flutter 视频。
实例变量初始化
#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)';
}
}
使用初始化形式参数
#为了简化将构造函数参数分配给实例变量的常见模式,Dart 有初始化形式参数。
在构造函数声明中,包括 this.<propertyName>
并省略主体。this
关键字引用当前实例。
当名称冲突存在时,使用 this
。否则,Dart 风格省略 this
。对于必须在初始化形式参数名称前加上 this
的生成构造函数来说,存在一个例外。
如本指南前面提到的,某些构造函数和构造函数的某些部分无法访问 this
。这些包括
- 工厂构造函数
- 初始化列表的右侧
- 传递给超类构造函数的参数
初始化形式参数也允许你初始化不可为空或 final
实例变量。这两种类型的变量都需要初始化或默认值。
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);
// Initializing formal parameters can also be optional.
PointB.optional([this.x = 0.0, this.y = 0.0]);
}
私有字段不能用作命名的初始化形式参数。
class PointB {
// ...
PointB.namedPrivate({required double x, required double y})
: _x = x,
_y = y;
// ...
}
这也适用于命名变量。
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
且仅在初始化变量的作用域内。
要执行无法在初始化列表中表达的逻辑,请创建一个 工厂构造函数 或一个具有该逻辑的 静态方法。然后,你可以将计算出的值传递给正常的构造函数。
构造函数参数可以设置为可为空,并且不进行初始化。
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)';
}
}
使用初始化列表
#在构造函数主体运行之前,你可以初始化实例变量。用逗号分隔初始值设定项。
// 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)');
}
要在开发过程中验证输入,请在初始化列表中使用 assert
。
Point.withAssert(this.x, this.y) : assert(x >= 0) {
print('In Point.withAssert(): ($x, $y)');
}
初始化列表有助于设置 final
字段。
以下示例在初始化列表中初始化了三个 final
字段。要执行代码,请单击运行。
import 'dart:math';
class Point {
final double x;
final double y;
final double distanceFromOrigin;
Point(double x, double y)
: x = x,
y = y,
distanceFromOrigin = sqrt(x * x + y * y);
}
void main() {
var p = Point(2, 3);
print(p.distanceFromOrigin);
}
构造函数继承
#子类或子类不会从其超类或直接父类继承构造函数。如果类没有声明构造函数,则它只能使用 默认构造函数.
类可以继承超类的参数。这些称为 超参数
构造函数的工作方式与调用静态方法链有些相似。每个子类都可以调用其超类的构造函数来初始化实例,就像子类可以调用超类的静态方法一样。此过程不会“继承”构造函数主体或签名。
非默认超类构造函数
#Dart 按以下顺序执行构造函数
- 初始化列表
- 超类的无名无参数构造函数
- 主类的无参数构造函数
如果超类缺少无名、无参数构造函数,请调用超类中的一个构造函数。在构造函数体(如果有)之前,在冒号 (:
) 后指定超类构造函数。
在以下示例中,Employee
类构造函数调用其超类 Person
的命名构造函数。要执行以下代码,请单击 **运行**。
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');
}
}
void main() {
var employee = Employee.fromJson({});
print(employee);
// Prints:
// in Person
// in Employee
// Instance of 'Employee'
}
由于 Dart 在调用构造函数之前评估传递给超类构造函数的参数,因此参数可以是表达式,例如函数调用。
class Employee extends Person {
Employee() : super.fromJson(fetchDefaultData());
// ···
}
超参数
#为了避免将每个参数传递到构造函数的超类调用中,请使用超类初始化参数将参数转发到指定的或默认的超类构造函数。您不能在 重定向构造函数 中使用此功能。超类初始化参数的语法和语义与 初始化形式参数 相同。
如果超类构造函数调用包含位置参数,则超类初始化参数不能是位置参数。
class Vector2d {
final double x;
final double y;
Vector2d(this.x, this.y);
}
class Vector3d extends Vector2d {
final double z;
// Forward the x and y parameters to the default super constructor like:
// Vector3d(final double x, final double y, this.z) : super(x, y);
Vector3d(super.x, super.y, this.z);
}
为了进一步说明,请考虑以下示例。
// If you invoke the super constructor (`super(0)`) with any
// positional arguments, using a super parameter (`super.x`)
// results in an error.
Vector3d.xAxisError(super.x): z = 0, super(0); // BAD
此命名构造函数尝试两次设置 x
值:一次在超类构造函数中,一次作为位置超类参数。由于两者都指向 x
位置参数,因此会导致错误。
当超类构造函数具有命名参数时,您可以将它们拆分为命名超类参数 (super.y
在下一个示例中) 和传递给超类构造函数调用的命名参数 (super.named(x: 0)
)。
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);
}
除非另有说明,否则本网站上的文档反映了 Dart 3.5.3。页面最后更新于 2024 年 8 月 4 日。 查看源代码 或 报告问题.