目录

构造函数

构造函数是创建类实例的特殊函数。

Dart 实现多种类型的构造函数。 除了默认构造函数外, 这些函数使用与其类相同的名称。

构造函数类型

#

生成式构造函数

#

要实例化一个类,请使用生成式构造函数。

dart
class Point {
  // 实例变量,用于保存点的坐标。
  double x;
  double y;

  // 带有初始化形式参数的生成式构造函数:
  Point(this.x, this.y);
}

默认构造函数

#

如果未声明构造函数,Dart 将使用默认构造函数。 默认构造函数是一个不带参数或名称的生成式构造函数。

命名构造函数

#

使用命名构造函数可以为一个类实现多个构造函数,或提供额外的清晰度:

dart
const double xOrigin = 0;
const double yOrigin = 0;

class Point {
  final double x;
  final double y;

  // 设置 x 和 y 实例变量
  // 在构造函数体运行之前。
  Point(this.x, this.y);

  // 命名构造函数
  Point.origin()
      : x = xOrigin,
        y = yOrigin;
}

子类不会继承超类的命名构造函数。 要创建具有在超类中定义的命名构造函数的子类, 请在子类中实现该构造函数。

常量构造函数

#

如果您的类生成不变的对象,请将这些对象设为编译时常量。 要将对象设为编译时常量,请定义一个 const 构造函数,并将所有实例变量设置为 final

dart
class ImmutablePoint {
  static const ImmutablePoint origin = ImmutablePoint(0, 0);

  final double x, y;

  const ImmutablePoint(this.x, this.y);
}

常量构造函数并不总是创建常量。 它们可能在非 const 上下文中被调用。 要了解更多信息,请参阅有关 使用构造函数 的部分。

重定向构造函数

#

构造函数可能会重定向到同一类中的另一个构造函数。 重定向构造函数具有空主体。 构造函数在冒号 (:) 后使用 this 而不是类名。

dart
class Point {
  double x, y;

  // 此类的主要构造函数。
  Point(this.x, this.y);

  // 委托给主构造函数。
  Point.alongXAxis(double x) : this(x, 0);
}

工厂构造函数

#

当遇到以下两种实现构造函数的情况之一时, 使用 factory 关键字:

  • 构造函数并不总是创建其类的新的实例。 虽然工厂构造函数不能返回 null , 但它可能会返回:

    • 从缓存中返回现有实例,而不是创建新的实例
    • 子类型的新的实例
  • 您需要在构造实例之前执行非平凡的工作。 这可能包括检查参数或执行初始化列表中无法处理的任何其他处理。

以下示例包含两个工厂构造函数。

  • Logger 工厂构造函数从缓存中返回对象。
  • Logger.fromJson 工厂构造函数从 JSON 对象初始化 final 变量。
dart
class Logger {
  final String name;
  bool mute = false;

  // _cache 是库私有的,这要感谢其名称前面的 _。
  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);
  }
}

像任何其他构造函数一样使用工厂构造函数:

dart
var logger = Logger('UI');
logger.log('Button clicked');

var logMap = {'name': 'UI'};
var loggerJson = Logger.fromJson(logMap);

重定向工厂构造函数

#

重定向工厂构造函数指定在调用重定向构造函数时要使用的另一个类的构造函数调用。

dart
factory Listenable.merge(List<Listenable> listenables) = _MergingListenable

看起来普通的工厂构造函数可以创建并返回其他类的实例。 这将使重定向工厂变得不必要。 重定向工厂具有以下几个优点:

  • 抽象类可以提供一个使用另一个类的常量构造函数的常量构造函数。
  • 重定向工厂构造函数避免了转发器重复形式参数及其默认值的需要。

构造函数撕裂

#

Dart 允许您提供构造函数作为参数而不调用它。 称为 撕裂 (因为您 撕裂 了括号) 用作闭包,它使用相同的参数调用构造函数。

如果撕裂是具有与方法接受的相同签名和返回类型的构造函数,则可以使用撕裂作为参数或变量。

撕裂与 lambda 或匿名函数不同。 lambda 用作构造函数的包装器,而撕裂是构造函数。

使用撕裂

gooddart
// 为命名构造函数使用撕裂:
var strings = charCodes.map(String.fromCharCode);

// 为未命名构造函数使用撕裂:
var buffers = charCodes.map(StringBuffer.new);

不是 lambda

baddart
// 不是为命名构造函数使用 lambda:
var strings = charCodes.map((code) => String.fromCharCode(code));

// 不是为未命名构造函数使用 lambda:
var buffers = charCodes.map((code) => StringBuffer(code));

有关更多讨论,请观看此关于撕裂的 Decoding Flutter 视频。


Dart Tear-offs | Decoding Flutter

实例变量初始化

#

Dart 可以通过三种方式初始化变量。

在声明中初始化实例变量

#

声明变量时初始化实例变量。

dart
class PointA {
  double x = 1.0;
  double y = 2.0;

  // 隐式默认构造函数将这些变量设置为 (1.0,2.0)
  // PointA();

  @override
  String toString() {
    return 'PointA($x,$y)';
  }
}

使用初始化形式参数

#

为了简化将构造函数参数赋值给实例变量的常见模式,Dart 提供了 初始化形式参数

在构造函数声明中,包含 this.<propertyName> 并省略主体。 this 关键字指代当前实例。

当发生名称冲突时,使用 this 。 否则,Dart 风格会省略 this 。 生成式构造函数存在一个例外,您必须在初始化形式参数名称前加上 this

如本指南前面所述,某些构造函数和构造函数的某些部分无法访问 this 。这些包括:

  • 工厂构造函数
  • 初始化列表的右侧
  • 超类构造函数的参数

初始化形式参数还可以初始化不可为空或 final 实例变量。 这两种类型的变量都需要初始化或默认值。

dart
class PointB {
  final double x;
  final double y;

  // 设置 x 和 y 实例变量
  // 在构造函数体运行之前。
  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; // 必须在构造函数中设置
  double y; // 必须在构造函数中设置

  // 带有默认值的初始化形式参数的生成式构造函数
  PointC.named({this.x = 1.0, this.y = 1.0});

  @override
  String toString() {
    return 'PointC.named($x,$y)';
  }
}

// 使用命名变量的构造函数。
final pointC = PointC.named(x: 2.0, y: 2.0);

从初始化形式参数引入的所有变量都是 final 的,并且仅在已初始化变量的作用域内。

要执行在初始化列表中无法表达的逻辑, 请创建一个 工厂构造函数 或具有该逻辑的 静态方法 。 然后,您可以将计算出的值传递给普通构造函数。

构造函数参数可以设置为可空,并且不会被初始化。

dart
class PointD {
  double? x; // 如果未在构造函数中设置则为 null
  double? y; // 如果未在构造函数中设置则为 null

  // 带有初始化形式参数的生成式构造函数
  PointD(this.x, this.y);

  @override
  String toString() {
    return 'PointD($x,$y)';
  }
}

使用初始化列表

#

在构造函数体运行之前,您可以初始化实例变量。 用逗号分隔初始化程序。

dart
// 初始化列表在构造函数体运行之前设置实例变量。
Point.fromJson(Map<String, double> json)
    : x = json['x']!,
      y = json['y']! {
  print('In Point.fromJson(): ($x, $y)');
}

要在开发过程中验证输入, 请在初始化列表中使用 assert

dart
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 按以下顺序执行构造函数:

  1. 初始化列表
  2. 超类的无名、无参构造函数
  3. 主类的无参构造函数

如果超类缺少无名、无参数的构造函数, 则调用超类中的一个构造函数。 在构造函数体(如果有)之前, 在冒号 (:) 后指定超类构造函数。

在下面的示例中, Employee 类的构造函数调用其超类 Person 的命名构造函数。要执行以下代码,请单击 运行

class Person {
  String? firstName;

  Person.fromJson(Map data) {
    print('in Person');
  }
}

class Employee extends Person {
  // Person 没有默认构造函数;
  // 您必须调用 super.fromJson()。
  Employee.fromJson(Map data) : super.fromJson(data) {
    print('in Employee');
  }
}

void main() {
  var employee = Employee.fromJson({});
  print(employee);
  // 打印:
  // in Person
  // in Employee
  // Instance of 'Employee'
}

当 Dart 在调用构造函数 之前 评估超类构造函数的参数时,参数可以是表达式,例如函数调用。

dart
class Employee extends Person {
  Employee() : super.fromJson(fetchDefaultData());
  // ···
}

超参数

#

为了避免将每个参数传递到构造函数的超调用中, 使用超初始化参数将参数转发到指定的或默认的超类构造函数。 您不能将此功能与 重定向构造函数 一起使用。 超初始化参数具有与 初始化形式参数 相同的语法和语义。

如果超构造函数调用包含位置参数, 则超初始化参数不能是位置参数。

dart
class Vector2d {
  final double x;
  final double y;

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

class Vector3d extends Vector2d {
  final double z;

  // 将 x 和 y 参数转发到默认的超构造函数,例如:
  // Vector3d(final double x, final double y, this.z) : super(x, y);
  Vector3d(super.x, super.y, this.z);
}

为了进一步说明,请考虑以下示例。

dart
  // 如果您使用任何位置参数调用超构造函数 (`super(0)`),
  // 使用超参数 (`super.x`) 会导致错误。
  Vector3d.xAxisError(super.x): z = 0, super(0); // BAD

此命名构造函数尝试两次设置 x 值: 一次在超构造函数中,一次作为位置超参数。 由于两者都指向 x 位置参数,因此会导致错误。

当超构造函数具有命名参数时,您可以将它们 拆分为命名超参数(在下一个示例中为 super.y ) 和超构造函数调用的命名参数( super.named(x: 0) )。

dart
class Vector2d {
  // ...
  Vector2d.named({required this.x, required this.y});
}

class Vector3d extends Vector2d {
  final double z;

  // 将 y 参数转发到命名的超构造函数,例如:
  // 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);
}