构造函数

Last updated: ... / Reads: 35 Edit

通过创建与其类同名的函数来声明构造函数(加上可选的附加标识符,如命名构造函数中所述)。

使用最常见的构造函数(生成构造函数)创建类的新实例,并初始化形式参数以实例化任何实例变量(如有必要):

class Point {
  double x = 0;
  double y = 0;

  // Generative constructor with initializing formal parameters:
  Point(this.x, this.y);
}

this 关键字指的是当前实例。

仅当存在名称冲突时才使用 this 。否则,Dart 样式会省略 this 。

初始化形式参数

Dart 具有初始化形式参数来简化将构造函数参数分配给实例变量的常见模式。直接在构造函数声明中使用 this.propertyName ,并省略正文。

初始化参数还允许您初始化不可为 null 的变量或 final 实例变量,这两个变量都必须初始化或提供默认值:

class Point {
  final double x;
  final double y;

  Point(this.x, this.y);
  // Sets the x and y instance variables
  // before the constructor body runs.
}

初始化形式引入的变量是隐式最终变量,并且仅在初始化列表的范围内。

如果您需要执行一些无法在初始值设定项列表中表达的逻辑,请使用该逻辑创建一个工厂构造函数(或静态方法),然后将计算值传递给普通构造函数。

默认构造函数

如果您没有声明构造函数,则会为您提供默认构造函数。默认构造函数没有参数,并调用超类中的无参数构造函数。

构造函数不会被继承

子类不会从其超类继承构造函数。未声明构造函数的子类仅具有默认(无参数、无名称)构造函数。

命名构造函数

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

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

class Point {
  final double x;
  final double y;

  Point(this.x, this.y);

  // Named constructor
  Point.origin()
      : x = xOrigin,
        y = yOrigin;
}

请记住,构造函数不是继承的,这意味着超类的命名构造函数不会被子类继承。如果您希望使用超类中定义的命名构造函数来创建子类,则必须在子类中实现该构造函数。

调用非默认超类构造函数

默认情况下,子类中的构造函数调用超类的未命名、无参数构造函数。超类的构造函数在构造函数体的开头被调用。如果还使用了初始值设定项列表,则它会在调用超类之前执行。综上所述,执行顺序如下:

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

如果超类没有未命名、无参数的构造函数,则必须手动调用超类中的构造函数之一。在冒号 ( : ) 之后、构造函数主体(如果有)之前指定超类构造函数。

在以下示例中,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(super.data) : super.fromJson() {
    print('in Employee');
  }
}

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

因为超类构造函数的参数是在调用构造函数之前计算的,所以参数可以是表达式,例如函数调用:

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

超类构造函数的参数无权访问 this 。例如,参数可以调用静态方法,但不能调用实例方法。

超参数

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

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);
}

如果超级构造函数调用已经具有位置参数,则超级初始化参数不能是位置参数,但它们始终可以命名:

class Vector2d {
  // ...

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

class Vector3d extends Vector2d {
  // ...

  // 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);
}

使用超级初始化参数需要至少 2.17 的语言版本。如果您使用的是早期语言版本,则必须手动传入所有超级构造函数参数。

初始化列表

除了调用超类构造函数之外,您还可以在构造函数主体运行之前初始化实例变量。用逗号分隔初始值设定项。

// 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 来验证输入。

Point.withAssert(this.x, this.y) : assert(x >= 0) {
  print('In Point.withAssert(): ($x, $y)');
}

设置最终字段时初始化列表很方便。以下示例初始化初始值设定项列表中的三个最终字段。单击“运行”以执行代码。

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);
}

重定向构造函数

有时,构造函数的唯一目的是重定向到同一类中的另一个构造函数。重定向构造函数的主体是空的,构造函数调用(使用 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);
}

常量构造函数

如果您的类生成永不改变的对象,则可以使这些对象成为编译时常量。为此,请定义 const 构造函数并确保所有实例变量均为 final 。

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

  final double x, y;

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

常量构造函数并不总是创建常量。有关详细信息,请参阅有关使用构造函数的部分。

工厂建设者

在实现并不总是创建其类的新实例的构造函数时,请使用 factory 关键字。例如,工厂构造函数可能会从缓存返回一个实例,也可能会返回一个子类型的实例。工厂构造函数的另一个用例是使用初始化列表中无法处理的逻辑来初始化最终变量。

处理最终变量后期初始化的另一种方法是使用 late final (小心!)。

在以下示例中, Logger 工厂构造函数从缓存返回对象, Logger.fromJson 工厂构造函数从 JSON 对象初始化最终变量。

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 。

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

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

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


Comments

Make a comment