【Flutter 1-11】Flutter手把手教程Dart語言——類、類的的成員變量和方法、類的構(gòu)造函數(shù)

Dart是一種面向?qū)ο蟮恼Z言,所有對象都是一個(gè)類的實(shí)例,而所有的類都繼承自Object類。每個(gè)除了Object類之外的類都只有一個(gè)超類,一個(gè)類的代碼可以在其它多個(gè)類繼承中重復(fù)使用。

類的實(shí)例變量

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

class Point {
  double x; // 聲明 double 變量 x 并初始化為 null。
  double y; // 聲明 double 變量 y 并初始化為 null
  double z = 0; // 聲明 double 變量 z 并初始化為 0。
}

所有未初始化的實(shí)例變量其值均為null

所有實(shí)例變量均會隱式地聲明一個(gè)Getter 方法,非final類型的實(shí)例變量還會隱式地聲明一個(gè)Setter方法

class Point {
  double x;
  double y;
}

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

如果你在聲明一個(gè)實(shí)例變量的時(shí)候就將其初始化(而不是在構(gòu)造函數(shù)或其它方法中),那么該實(shí)例變量的值就會在對象實(shí)例創(chuàng)建的時(shí)候被設(shè)置,該過程會在構(gòu)造函數(shù)以及它的初始化器列表執(zhí)行前。

訪問類的成員

對象的成員由函數(shù)和數(shù)據(jù)(即方法和實(shí)例變量)組成。方法的調(diào)用要通過對象來完成,這種方式可以訪問對象的函數(shù)和數(shù)據(jù)。
使用.來訪問對象的實(shí)例變量或方法:

var p = Point(2, 2);
// 獲取 y 值
assert(p.y == 2);
// 調(diào)用變量 p 的 distanceTo() 方法。
double distance = p.distanceTo(Point(4, 4));

使用 ?. 代替.可以避免因?yàn)樽筮叡磉_(dá)式為null 而導(dǎo)致的問題:

// If p is non-null, set a variable equal to its y value.
var a = p?.y;
方法

方法是對象提供行為的函數(shù)。

對象的實(shí)例方法可以訪問實(shí)例變量和this。下面的distanceTo()方法就是一個(gè)實(shí)例方法的例子:

import 'dart:math';

class Point {
  double x, y;

  Point(this.x, this.y);

  double distanceTo(Point other) {
    var dx = x - other.x;
    var dy = y - other.y;
    return sqrt(dx * dx + dy * dy);
  }
}
重寫類成員

子類可以重寫父類的實(shí)例方法(包括操作符)、 Getter 以及 Setter 方法。你可以使用 @override注解來表示你重寫了一個(gè)成員:

class SmartTelevision extends Television {
  @override
  void turnOn() {...}
  // ···
}
noSuchMethod 方法

如果調(diào)用了對象中不存在的方法或?qū)嵗兞繉|發(fā)noSuchMethod 方法,你可以重寫noSuchMethod方法來追蹤和記錄這一行為:

class A {
  // 除非你重寫 noSuchMethod,否則調(diào)用一個(gè)不存在的成員會導(dǎo)致 NoSuchMethodError。
  @override
  void noSuchMethod(Invocation invocation) {
    print('你嘗試使用一個(gè)不存在的成員:' + '${invocation.memberName}');
  }
}

你不能調(diào)用一個(gè)未實(shí)現(xiàn)的方法除非滿足下面其中的一個(gè)條件:

  • 接收方是靜態(tài)的dynamic 類型。
  • 接收方具有靜態(tài)類型,定義了未實(shí)現(xiàn)的方法,并且接收方的動態(tài)類型實(shí)現(xiàn)了noSuchMethod方法且具體的實(shí)現(xiàn)與Object中的不同。

類的靜態(tài)變量和靜態(tài)方法

使用關(guān)鍵字static可以聲明類變量或類方法。

靜態(tài)變量

靜態(tài)變量(即類變量)常用于聲明類范圍內(nèi)所屬的狀態(tài)變量和常量:

class Queue {
  static const initialCapacity = 16;
  // ···
}
void main() {
  assert(Queue.initialCapacity == 16);
}

靜態(tài)變量在其首次被使用的時(shí)候才被初始化。

靜態(tài)方法

靜態(tài)方法(即類方法)不能被一個(gè)類的實(shí)例訪問,同樣地,靜態(tài)方法內(nèi)也不可以使用關(guān)鍵字this

import 'dart:math';
class Point {
  double x, y;
  Point(this.x, this.y);
  static double distanceBetween(Point a, Point b) {
    var dx = a.x - b.x;
    var dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
  }
}

void main() {
  var a = Point(2, 2);
  var b = Point(4, 4);
  var distance = Point.distanceBetween(a, b);
  assert(2.8 < distance && distance < 2.9);
  print(distance);
}

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

可以使用構(gòu)造函數(shù)來創(chuàng)建一個(gè)對象。構(gòu)造函數(shù)的命名方式可以為類名或類名.標(biāo)識符的形式。例如下述代碼分別使用Point()Point.fromJson()兩種構(gòu)造器來創(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});

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

var p = const ImmutablePoint(2, 2);

兩個(gè)使用相同構(gòu)造函數(shù)相同參數(shù)值構(gòu)造的編譯時(shí)常量是同一個(gè)對象:

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

assert(identical(a, b)); // 它們是同一個(gè)實(shí)例 (They are the same instance!)

根據(jù)使用常量上下文的場景,你可以省略掉構(gòu)造函數(shù)或字面量前的const 關(guān)鍵字。例如下面的例子中我們創(chuàng)建了一個(gè)常量Map

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

根據(jù)上下文,你可以只保留第一個(gè)const關(guān)鍵字,其余的全部省略:

// 只需要一個(gè) const 關(guān)鍵字,其它的則會隱式地根據(jù)上下文進(jìn)行關(guān)聯(lián)。
const pointAndLine = {
  'point': [ImmutablePoint(0, 0)],
  'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};

但是如果無法根據(jù)上下文判斷是否可以省略const,則不能省略掉 const關(guān)鍵字,否則將會創(chuàng)建一個(gè)常量對象 例如:

var a = const ImmutablePoint(1, 1); // 創(chuàng)建一個(gè)常量 (Creates a constant)
var b = ImmutablePoint(1, 1); // 不會創(chuàng)建一個(gè)常量
assert(!identical(a, b)); // 這兩變量并不相同 

獲取對象的類型

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

var = Point(2, 2);
print('The type of a is ${a.runtimeType}');

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

聲明一個(gè)與類名一樣的函數(shù)即可聲明一個(gè)構(gòu)造函數(shù)(對于命名式構(gòu)造函數(shù) 還可以添加額外的標(biāo)識符)。大部分的構(gòu)造函數(shù)形式是生成式構(gòu)造函數(shù),其用于創(chuàng)建一個(gè)類的實(shí)例:

class Point {
  double x, y;
  Point(double x, double y) {
    // 還會有更好的方式來實(shí)現(xiàn)此邏輯,敬請期待。
    this.x = x;
    this.y = y;
  }
}

使用 this 關(guān)鍵字引用當(dāng)前實(shí)例。
對于大多數(shù)編程語言來說在構(gòu)造函數(shù)中為實(shí)例變量賦值的過程都是類似的,而Dart則提供了一種特殊的語法糖來簡化該步驟:

class Point {
  double x, y;
  // 在構(gòu)造函數(shù)體執(zhí)行前用于設(shè)置 x 和 y 的語法糖。
  Point(this.x, this.y);
}
默認(rèn)構(gòu)造函數(shù)

如果你沒有聲明構(gòu)造函數(shù),那么Dart會自動生成一個(gè)無參數(shù)的構(gòu)造函數(shù)并且該構(gòu)造函數(shù)會調(diào)用其父類的無參數(shù)構(gòu)造方法。

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

子類不會繼承父類的構(gòu)造函數(shù),如果子類沒有聲明構(gòu)造函數(shù),那么只會有一個(gè)默認(rèn)無參數(shù)的構(gòu)造函數(shù)。

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

可以為一個(gè)類聲明多個(gè)命名式構(gòu)造函數(shù)來表達(dá)更明確的意圖:

class Point {
  double x, y;

  Point(this.x, this.y);

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

構(gòu)造函數(shù)是不能被繼承的,這將意味著子類不能繼承父類的命名式構(gòu)造函數(shù),如果你想在子類中提供一個(gè)與父類命名構(gòu)造函數(shù)名字一樣的命名構(gòu)造函數(shù),則需要在子類中顯式地聲明。

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

默認(rèn)情況下,子類的構(gòu)造函數(shù)會調(diào)用父類的匿名無參數(shù)構(gòu)造方法,并且該調(diào)用會在子類構(gòu)造函數(shù)的函數(shù)體代碼執(zhí)行前,如果子類構(gòu)造函數(shù)還有一個(gè)初始化列表,那么該初始化列表會在調(diào)用父類的該構(gòu)造函數(shù)之前被執(zhí)行,總的來說,這三者的調(diào)用順序如下:

  1. 初始化列表
  2. 父類的無參數(shù)構(gòu)造函數(shù)
  3. 當(dāng)前類的構(gòu)造函數(shù)

如果父類沒有匿名無參數(shù)構(gòu)造函數(shù),那么子類必須調(diào)用父類的其中一個(gè)構(gòu)造函數(shù),為子類的構(gòu)造函數(shù)指定一個(gè)父類的構(gòu)造函數(shù)只需在構(gòu)造函數(shù)體前使用:指定。

因?yàn)閰?shù)會在子類構(gòu)造函數(shù)被執(zhí)行前傳遞給父類的構(gòu)造函數(shù),因此該參數(shù)也可以是一個(gè)表達(dá)式,比如一個(gè)函數(shù):

class Employee extends Person {
  Employee() : super.fromJson(defaultData);
  // ···
}
初始化列表

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

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

在開發(fā)模式下,你可以在初始化列表中使用assert來驗(yàn)證輸入數(shù)據(jù):

Point.withAssert(this.x, this.y) : assert(x >= 0) {
  print('In Point.withAssert(): ($x, $y)');
}
重定向構(gòu)造函數(shù)

有時(shí)候類中的構(gòu)造函數(shù)會調(diào)用類中其它的構(gòu)造函數(shù),該重定向構(gòu)造函數(shù)沒有函數(shù)體,只需在函數(shù)簽名后使用:指定需要重定向到的其它構(gòu)造函數(shù)即可:

class Point {
  double x, y;

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

  // 委托實(shí)現(xiàn)給主構(gòu)造函數(shù)。
  Point.alongXAxis(double x) : this(x, 0);
}
常量構(gòu)造函數(shù)

如果類生成的對象都是不會變的,那么可以在生成這些對象時(shí)就將其變?yōu)榫幾g時(shí)常量。你可以在類的構(gòu)造函數(shù)前加上const關(guān)鍵字并確保所有實(shí)例變量均為final來實(shí)現(xiàn)該功能。

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

  final double x, y;

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

常量構(gòu)造函數(shù)創(chuàng)建的實(shí)例并不總是常量。

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

使用factory關(guān)鍵字標(biāo)識類的構(gòu)造函數(shù)將會令該構(gòu)造函數(shù)變?yōu)楣S構(gòu)造函數(shù),這將意味著使用該構(gòu)造函數(shù)構(gòu)造類的實(shí)例時(shí)并非總是會返回新的實(shí)例對象。例如,工廠構(gòu)造函數(shù)可能會從緩存中返回一個(gè)實(shí)例,或者返回一個(gè)子類型的實(shí)例。

在如下的示例中,Logger的工廠構(gòu)造函數(shù)從緩存中返回對象,和 Logger.fromJson工廠構(gòu)造函數(shù)從JSON對象中初始化一個(gè)最終變量。

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

  // _cache 變量是庫私有的,因?yàn)樵谄涿智懊嬗邢聞澗€。
  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);
  }
}

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

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

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

抽象類

使用關(guān)鍵字abstract 標(biāo)識類可以讓該類成為抽象類,抽象類將無法被實(shí)例化。抽象類常用于聲明接口方法、有時(shí)也會有具體的方法實(shí)現(xiàn)。如果想讓抽象類同時(shí)可被實(shí)例化,可以為其定義工廠構(gòu)造函數(shù)。

抽象類常常會包含抽象方法。下面是一個(gè)聲明具有抽象方法的抽象類示例:

// 該類被聲明為抽象的,因此它不能被實(shí)例化。
abstract class AbstractContainer {
  // 定義構(gòu)造函數(shù)、字段、方法等……
  void updateChildren(); // 抽象方法。
}

隱式接口

每一個(gè)類都隱式地定義了一個(gè)接口并實(shí)現(xiàn)了該接口,這個(gè)接口包含所有這個(gè)類的實(shí)例成員以及這個(gè)類所實(shí)現(xiàn)的其它接口。如果想要?jiǎng)?chuàng)建一個(gè)A類支持調(diào)用B類的API且不想繼承B類,則可以實(shí)現(xiàn)B類的接口。

一個(gè)類可以通過關(guān)鍵字implements來實(shí)現(xiàn)一個(gè)或多個(gè)接口并實(shí)現(xiàn)每個(gè)接口定義的 API:

// Person 類的隱式接口中包含 greet() 方法。
class Person {
  // _name 變量同樣包含在接口中,但它只是庫內(nèi)可見的。
  final _name;

  // 構(gòu)造函數(shù)不在接口中。
  Person(this._name);

  // greet() 方法在接口中。
  String greet(String who) => '你好,$who。我是$_name。';
}

// Person 接口的一個(gè)實(shí)現(xiàn)。
class Impostor implements Person {
  get _name => '';

  String greet(String who) => '你好$who。你知道我是誰嗎?';
}

String greetBob(Person person) => person.greet('小芳');

void main() {
  print(greetBob(Person('小蕓')));
  print(greetBob(Impostor()));
}

如果需要實(shí)現(xiàn)多個(gè)類接口,可以使用逗號分割每個(gè)接口類:

class Point implements Comparable, Location {

}

擴(kuò)展一個(gè)類

使用extends關(guān)鍵字來創(chuàng)建一個(gè)子類,并可使用super關(guān)鍵字引用一個(gè)父類:

class Television {
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }
}
class SmartTelevision extends Television {
  void turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    _initializeMemory();
    _upgradeApps();
  }
}

使用 Mixin 為類添加功能

Mixin是一種在多重繼承中復(fù)用某個(gè)類中代碼的方法模式。
使用with關(guān)鍵字并在其后跟上Mixin類的名字來使用Mixin模式:

class Musician extends Performer with Musical {
  // ···
}

class Maestro extends Person
    with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}

定義一個(gè)類繼承自Object并且不為該類定義構(gòu)造函數(shù),這個(gè)類就是 Mixin類,除非你想讓該類與普通的類一樣可以被正常地使用,否則可以使用關(guān)鍵字mixin替代class讓其成為一個(gè)單純的Mixin類:

mixin Musical {
  bool canPlayPiano = false;
  bool canCompose = false;
  bool canConduct = false;

  void entertainMe() {
    if (canPlayPiano) {
      print('Playing piano');
    } else if (canConduct) {
      print('Waving hands');
    } else {
      print('Humming to self');
    }
  }
}

可以使用關(guān)鍵字on來指定哪些類可以使用該Mixin類,比如有 MixinA,但是A只能被B類使用,則可以這樣定義 `A:

class Musician {
  // ...
}
mixin MusicalPerformer on Musician {
  // ...
}
class SingerDancer extends Musician with MusicalPerformer {
  // ...
}

Extension 方法

Dart 2.7 中引入的Extension方法是向現(xiàn)有庫添加功能的一種方式。
這里是一個(gè)在 String 中使用 extension方法的樣例,我們?nèi)∶麨?parseInt(),它在 string_apis.dart 中定義:

extension NumberParsing on String {
  int parseInt() {
    return int.parse(this);
  }

  double parseDouble() {
    return double.parse(this);
  }
}

然后使用string_apis.dart里面的parseInt()方法

import 'string_apis.dart';
print('42'.padLeft(5)); // Use a String method.
print('42'.parseInt()); // Use an extension method.
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容