Dart - 类(变量声明、构造函数)

这节详细介绍如何定义类的变量声明构造方法

实例变量

下面是声明实例变量的示例:

class Point {
  num x; // 声明实例变量 x 并初始化为 null。
  num y; // 声明实例变量 y 并初始化为 null。
  num z = 0; // 声明实例变量 z 并初始化为 0。
}

备忘:"所有未初始化的实例变量其值均为 null。"

所有实例变量均会隐式地声明一个 Getter 方法,非 final 类型的实例变量还会隐式地声明一个 Setter 方法。你可以查阅 Getter 和 Setter

class Point {
  num x;
  num y;
}

void main() {
  var point = Point();
  point.x = 4; // 使用 x 的 Setter 方法。
  assert(point.x == 4); // 使用 x 的 Getter 方法。
  assert(point.y == null); // 默认值为 null。
}

备忘:
如果你在声明一个实例变量的时候就将其初始化(而不是在构造函数或其它方法中),那么该实例变量的值就会在对象实例创建的时候被设置,该过程会在构造函数以及它的初始化器列表执行前。

构造函数

声明一个与类名一样的函数即可声明一个构造函数(对于命名式构造函数还可以添加额外的标识符)。大部分的构造函数形式是生成式构造函数,其用于创建一个类的实例:

class Point {
  num x, y;

  Point(num x, num y) {
    // 还会有更好的方式来实现此逻辑,敬请期待。
    this.x = x;
    this.y = y;
  }
}

当且仅当命名冲突时使用 this 关键字才有意义,否则 Dart 会忽略 this 关键字。

对于大多数编程语言来说在构造函数中为实例变量赋值的过程都是类似的,而 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;
 }
}

跟构造函数的语法糖一样,也可以写成:

class Point {
  num x, y;

  Point(this.x, this.y);

  // 命名式构造函数
  Point.origin(this.x, this.y) ;
}

备忘:

    1. 记住构造函数是不能被继承的,这将意味着子类不能继承父类的命名式构造函数,如果你想在子类中提供一个与父类命名构造函数名字一样的命名构造函数,则需要在子类中显式地声明。
    1. 如果声明有参构造函数,这时就没有无参构造函数,也无法显示声明,因为类型构造函数只有一个;如果你声明了命名式的构造函数,这时就没有无参构造函数了,但你可以显示地声明出来。
    1. 第1点不能函数名重名的原因是:C++ 与 Java 的做法是,提供函数的重载,即提供同名但参数不同的函数。但 Dart 认为重载会导致混乱,因此从设计之初就不支持重载,而是提供了可选命名参数和可选参数。

调用父类非默认构造函数

默认情况下,子类的构造函数会调用父类的匿名无参数构造方法,并且该调用会在子类构造函数的函数体代码执行前,如果子类构造函数还有一个初始化列表,那么该初始化列表会在调用父类的该构造函数之前被执行,总的来说,这三者的调用顺序如下:

    1. 初始化列表
    1. 父类的无参数构造函数
    1. 当前类的构造函数

如果父类没有匿名无参数构造函数,那么子类必须调用父类的其中一个构造函数,为子类的构造函数指定一个父类的构造函数只需在构造函数体前使用(:)指定。

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

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

备忘:

    1. 构造函数声明主体后面接:可以调用父类的构造方法,用super代替父类的类型,如super()super.fromJson(),用this可以接本类的其他构造方法,很常用很强大,要记住。
    1. 传递给父类构造函数的参数不能使用this关键字,因为在参数传递的这一步骤,子类构造函数尚未执行,子类的实例对象也就还未初始化,因此所有的实例成员都不能被访问,但是类成员可以。
    1. 构造函数声明主体后面接:不仅仅是接父类构造函数,还可以接表达式、初始化列表(下面会介绍)。

初始化列表

刚才我们提到初始化列表会在父类构造函数前执行,到底什么是初始化列表?

//初始化列表必须在父类表达式声明之前
Employee.fromJson(Map data) : employeeName = 'hahha',super(){
    print('in Employee');
  }

除了调用父类构造函数之外,还可以在构造函数体执行之前初始化实例变量,每个实例变量之间使用逗号分隔,这就是初始化列表:

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

初始化列表表达式 = 右边的语句不能使用 this 关键字。

初始化列表用来设置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);
}

这个语法糖和命名构造函数共同实现多个构造函数的实现,比起只用命名构造函数实现,委托实现可以帮忙实现重复的初始化代码。

委托后面不能再加大括号:

Employee.fromJson(Map data) : this();

常量构造函数

如果类生成的对象都是不会变的,那么可以在生成这些对象时就将其变为编译时常量。你可以在类的构造函数前加上const关键字并确保所有实例变量均为final来实现该功能。

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

  final num x, y;

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

工厂构造函数

使用factory关键字标识类的构造函数将会令该构造函数变为工厂构造函数,这将意味着使用该构造函数构造类的实例时并非总是会返回新的实例对象。例如,工厂构造函数可能会从缓存中返回一个实例,或者返回一个子类型的实例。

以下示例演示了从缓存中返回对象的工厂构造函数:

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

  Logger._internal(this.name);

  void log(String msg) {
    if (!mute) print(msg);
  }
}

在工厂构造函数中无法访问 this。

工厂构造函的调用方式与其他构造函数一样:


var logger = Logger('UI');
logger.log('Button clicked');
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,711评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,079评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,194评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,089评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,197评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,306评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,338评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,119评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,541评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,846评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,014评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,694评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,322评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,026评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,257评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,863评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,895评论 2 351

推荐阅读更多精彩内容