類(上)

2021-04-16 17:45 更新

Dart 是一種基于類和 mixin 繼承機(jī)制的面向?qū)ο蟮恼Z言。 每個(gè)對象都是一個(gè)類的實(shí)例,所有的類都繼承于 Object. 。 基于 * Mixin 繼承* 意味著每個(gè)類(除 Object 外) 都只有一個(gè)超類, 一個(gè)類中的代碼可以在其他多個(gè)繼承類中重復(fù)使用。


使用類的成員變量

對象是由函數(shù)和數(shù)據(jù)(即方法和實(shí)例變量)組成。 方法的調(diào)用要通過對象來完成: 調(diào)用的方法可以訪問其對象的其他函數(shù)和數(shù)據(jù)。

使用 (.) 來引用實(shí)例對象的變量和方法:

var p = Point(2, 2);

// 為實(shí)例的變量 y 設(shè)置值。
p.y = 3;

// 獲取變量 y 的值。
assert(p.y == 3);

// 調(diào)用 p 的 distanceTo() 方法。
num distance = p.distanceTo(Point(4, 4));

使用 ?. 來代替 . , 可以避免因?yàn)樽筮厡ο罂赡転?null , 導(dǎo)致的異常:

// 如果 p 為 non-null,設(shè)置它變量 y 的值為 4。
p?.y = 4;


使用構(gòu)造函數(shù)

通過 構(gòu)造函數(shù) 創(chuàng)建對象。 構(gòu)造函數(shù)的名字可以是 ClassName 或者 ClassName.identifier。例如, 以下代碼使用 Point 和 Point.fromJson() 構(gòu)造函數(shù)創(chuàng)建 Point 對象:

var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});

以下代碼具有相同的效果, 但是構(gòu)造函數(shù)前面的的 new 關(guān)鍵字是可選的:

var p1 = new Point(2, 2);
var p2 = new Point.fromJson({'x': 1, 'y': 2});

版本提示: 在 Dart 2 中 new 關(guān)鍵字變成了可選的。

一些類提供了常量構(gòu)造函數(shù)。 使用常量構(gòu)造函數(shù),在構(gòu)造函數(shù)名之前加 const 關(guān)鍵字,來創(chuàng)建編譯時(shí)常量時(shí):

var p = const ImmutablePoint(2, 2);

構(gòu)造兩個(gè)相同的編譯時(shí)常量會產(chǎn)生一個(gè)唯一的, 標(biāo)準(zhǔn)的實(shí)例:

var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);

assert(identical(a, b)); // 它們是同一個(gè)實(shí)例。

在 常量上下文 中, 構(gòu)造函數(shù)或者字面量前的 const 可以省略。 例如,下面代碼創(chuàng)建了一個(gè) const 類型的 map 對象:

// 這里有很多的 const 關(guān)鍵字。
const pointAndLine = const {
  'point': const [const ImmutablePoint(0, 0)],
  'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};

保留第一個(gè) const 關(guān)鍵字,其余的全部省略:

// 僅有一個(gè) const ,由該 const 建立常量上下文。
const pointAndLine = {
  'point': [ImmutablePoint(0, 0)],
  'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};

如果常量構(gòu)造函數(shù)在常量上下文之外, 且省略了 const 關(guān)鍵字, 此時(shí)創(chuàng)建的對象是非常量對象:

var a = const ImmutablePoint(1, 1); // 創(chuàng)建一個(gè)常量對象
var b = ImmutablePoint(1, 1); // 創(chuàng)建一個(gè)非常量對象

assert(!identical(a, b)); // 兩者不是同一個(gè)實(shí)例!

版本提示: 在 Dart 2 中,一個(gè)常量上下文中的 const 關(guān)鍵字可以被省略。


獲取對象的類型

使用對象的 runtimeType 屬性, 可以在運(yùn)行時(shí)獲取對象的類型, runtimeType 屬性回返回一個(gè) Type 對象。

print('The type of a is ${a.runtimeType}');

到目前為止,我們已經(jīng)解了如何_使用_類。 本節(jié)的其余部分將介紹如何_實(shí)現(xiàn)_一個(gè)類。


實(shí)例變量

下面是聲明實(shí)例變量的示例:

class Point {
  num x; // 聲明示例變量 x,初始值為 null 。
  num y; // 聲明示例變量 y,初始值為 null 。
  num z = 0; // 聲明示例變量 z,初始值為 0 。
}

未初始化實(shí)例變量的默認(rèn)人值為 “null” 。

所有實(shí)例變量都生成隱式 getter 方法。 非 final 的實(shí)例變量同樣會生成隱式 setter 方法。 有關(guān)更多信息,參考 Getters 和 setters.

class Point {
  num x;
  num y;
}

void main() {
  var point = Point();
  point.x = 4; // Use the setter method for x.
  assert(point.x == 4); // Use the getter method for x.
  assert(point.y == null); // Values default to null.
}

如果在聲明時(shí)進(jìn)行了示例變量的初始化, 那么初始化值會在示例創(chuàng)建時(shí)賦值給變量, 該賦值過程在構(gòu)造函數(shù)及其初始化列表執(zhí)行之前。


構(gòu)造函數(shù)

通過創(chuàng)建一個(gè)與其類同名的函數(shù)來聲明構(gòu)造函數(shù) (另外,還可以附加一個(gè)額外的可選標(biāo)識符,如 命名構(gòu)造函數(shù) 中所述)。 下面通過最常見的構(gòu)造函數(shù)形式, 即生成構(gòu)造函數(shù), 創(chuàng)建一個(gè)類的實(shí)例:

class Point {
  num x, y;

  Point(num x, num y) {
    // 還有更好的方式來實(shí)現(xiàn)下面代碼,敬請關(guān)注。
    this.x = x;
    this.y = y;
  }
}

使用 this 關(guān)鍵字引用當(dāng)前實(shí)例。

提示: 近當(dāng)存在命名沖突時(shí),使用 this 關(guān)鍵字。 否則,按照 Dart 風(fēng)格應(yīng)該省略 this 。

通常模式下,會將構(gòu)造函數(shù)傳入的參數(shù)的值賦值給對應(yīng)的實(shí)例變量, Dart 自身的語法糖精簡了這些代碼:

class Point {
  num x, y;

  // 在構(gòu)造函數(shù)體執(zhí)行前,
  // 語法糖已經(jīng)設(shè)置了變量 x 和 y。
  Point(this.x, this.y);
}

默認(rèn)構(gòu)造函數(shù)

在沒有聲明構(gòu)造函數(shù)的情況下, Dart 會提供一個(gè)默認(rèn)的構(gòu)造函數(shù)。 默認(rèn)構(gòu)造函數(shù)沒有參數(shù)并會調(diào)用父類的無參構(gòu)造函數(shù)。

構(gòu)造函數(shù)不被繼承

子類不會繼承父類的構(gòu)造函數(shù)。 子類不聲明構(gòu)造函數(shù),那么它就只有默認(rèn)構(gòu)造函數(shù) (匿名,沒有參數(shù)) 。

命名構(gòu)造函數(shù)

使用命名構(gòu)造函數(shù)可為一個(gè)類實(shí)現(xiàn)多個(gè)構(gòu)造函數(shù), 也可以使用命名構(gòu)造函數(shù)來更清晰的表明函數(shù)意圖:

class Point {
  num x, y;

  Point(this.x, this.y);

  // 命名構(gòu)造函數(shù)
  Point.origin() {
    x = 0;
    y = 0;
  }
}

切記,構(gòu)造函數(shù)不能夠被繼承, 這意味著父類的命名構(gòu)造函數(shù)不會被子類繼承。 如果希望使用父類中定義的命名構(gòu)造函數(shù)創(chuàng)建子類, 就必須在子類中實(shí)現(xiàn)該構(gòu)造函數(shù)。

調(diào)用父類非默認(rèn)構(gòu)造函數(shù)

默認(rèn)情況下,子類的構(gòu)造函數(shù)會自動(dòng)調(diào)用父類的默認(rèn)構(gòu)造函數(shù)(匿名,無參數(shù))。 父類的構(gòu)造函數(shù)在子類構(gòu)造函數(shù)體開始執(zhí)行的位置被調(diào)用。 如果提供了一個(gè) initializer list (初始化參數(shù)列表), 則初始化參數(shù)列表在父類構(gòu)造函數(shù)執(zhí)行之前執(zhí)行。 總之,執(zhí)行順序如下:

  1. initializer list (初始化參數(shù)列表)
  2. superclass’s no-arg constructor (父類的無名構(gòu)造函數(shù))
  3. main class’s no-arg constructor (主類的無名構(gòu)造函數(shù))

如果父類中沒有匿名無參的構(gòu)造函數(shù), 則需要手工調(diào)用父類的其他構(gòu)造函數(shù)。 在當(dāng)前構(gòu)造函數(shù)冒號 (:) 之后,函數(shù)體之前,聲明調(diào)用父類構(gòu)造函數(shù)。

下面的示例中,Employee 類的構(gòu)造函數(shù)調(diào)用了父類 Person 的命名構(gòu)造函數(shù)。

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

由于父類的構(gòu)造函數(shù)參數(shù)在構(gòu)造函數(shù)執(zhí)行之前執(zhí)行, 所以參數(shù)可以是一個(gè)表達(dá)式或者一個(gè)方法調(diào)用:

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

警告: 調(diào)用父類構(gòu)造函數(shù)的參數(shù)無法訪問 this 。 例如,參數(shù)可以為靜態(tài)函數(shù)但是不能是實(shí)例函數(shù)。

初始化列表

除了調(diào)用超類構(gòu)造函數(shù)之外, 還可以在構(gòu)造函數(shù)體執(zhí)行之前初始化實(shí)例變量。 各參數(shù)的初始化用逗號分隔。

// 在構(gòu)造函數(shù)體執(zhí)行之前,
// 通過初始列表設(shè)置實(shí)例變量。
Point.fromJson(Map<String, num> json)
    : x = json['x'],
      y = json['y'] {
  print('In Point.fromJson(): ($x, $y)');
}

警告: 初始化程序的右側(cè)無法訪問 this 。

在開發(fā)期間, 可以使用 assert 來驗(yàn)證輸入的初始化列表。

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

使用初始化列表可以很方便的設(shè)置 final 字段。 下面示例演示了,如何使用初始化列表初始化設(shè)置三個(gè) 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);
}


重定向構(gòu)造函數(shù)

有時(shí)構(gòu)造函數(shù)的唯一目的是重定向到同一個(gè)類中的另一個(gè)構(gòu)造函數(shù)。 重定向構(gòu)造函數(shù)的函數(shù)體為空, 構(gòu)造函數(shù)的調(diào)用在冒號 (:) 之后。

class Point {
  num x, y;

  // 類的主構(gòu)造函數(shù)。
  Point(this.x, this.y);

  // 指向主構(gòu)造函數(shù)
  Point.alongXAxis(num x) : this(x, 0);
}

常量構(gòu)造函數(shù)

如果該類生成的對象是固定不變的, 那么就可以把這些對象定義為編譯時(shí)常量。 為此,需要定義一個(gè) const 構(gòu)造函數(shù), 并且聲明所有實(shí)例變量為 final。

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

  final num x, y;

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

常量構(gòu)造函數(shù)創(chuàng)建的實(shí)例并不總是常量。 更多內(nèi)容,查看 使用構(gòu)造函數(shù) 章節(jié)。

工廠構(gòu)造函數(shù)

當(dāng)執(zhí)行構(gòu)造函數(shù)并不總是創(chuàng)建這個(gè)類的一個(gè)新實(shí)例時(shí),則使用 factory 關(guān)鍵字。 例如,一個(gè)工廠構(gòu)造函數(shù)可能會返回一個(gè) cache 中的實(shí)例, 或者可能返回一個(gè)子類的實(shí)例。

以下示例演示了從緩存中返回對象的工廠構(gòu)造函數(shù):

class Logger {
  final String name;
  bool mute = false;

  // 從命名的 _ 可以知,
  // _cache 是私有屬性。
  static final Map<String, Logger> _cache =
      <String, Logger>{};

  factory Logger(String name) {
    if (_cache.containsKey(name)) {
      return _cache[name];
    } else {
      final logger = Logger._internal(name);
      _cache[name] = logger;
      return logger;
    }
  }

  Logger._internal(this.name);

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

提示: 工廠構(gòu)造函數(shù)無法訪問 this。

工廠構(gòu)造函的調(diào)用方式與其他構(gòu)造函數(shù)一樣:

var logger = Logger('UI');
logger.log('Button clicked');
以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號