Dart 2 (七) 類

Dart是一種面向?qū)ο蟮恼Z言棋电,帶有類和基于mixin的繼承栗涂。每個對象都是類的實例,所有類都是從對象繼承而來丹拯≌境基于mixin的繼承意味著,盡管每個類(對象除外)都只有一個超類乖酬,但類主體可以在多個類層次結(jié)構(gòu)中重用死相。

使用類成員

對象具有由函數(shù)和數(shù)據(jù)(分別是方法和實例變量)組成的成員。當(dāng)您調(diào)用一個方法時咬像,您在一個對象上調(diào)用它:該方法可以訪問該對象的函數(shù)和數(shù)據(jù)算撮。
使用點(.)指向?qū)嵗兞炕蚍椒?

var p = Point(2, 2);

// Set the value of the instance variable y.
p.y = 3;

// Get the value of y.
assert(p.y == 3);

// Invoke distanceTo() on p.
num distance = p.distanceTo(Point(4, 4));

相對于 . 使用 ?. 可避免最左邊的操作數(shù)為空時出現(xiàn)異常:

// If p is non-null, set its y value to 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)建對象:

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ù)創(chuàng)建編譯時常量狸驳,請將const關(guān)鍵字放在構(gòu)造函數(shù)名之前:

var p = const ImmutablePoint(2, 2);

構(gòu)造兩個相同的編譯時常量會導(dǎo)致一個單獨的實例:

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

assert(identical(a, b)); // They are the same instance!

在常量上下文中预明,可以省略構(gòu)造函數(shù)或文字前面的常量。例如耙箍,看看這段代碼撰糠,它創(chuàng)建了一個const map:

const pointAndLine = const {
  'point': const [const ImmutablePoint(0, 0)],
  'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};

版本提示:在Dart 2中,const關(guān)鍵字在一個恒定的上下文中成為可選的辩昆。

獲取對象的類型

要在運行時獲得對象的類型阅酪,可以使用對象的runtimeType屬性,該屬性返回類型對象汁针。

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

到這里术辐,您已經(jīng)看到了如何使用類。本節(jié)的其余部分將展示如何實現(xiàn)類施无。

實例變量

下面是如何聲明實例變量:

class Point {
  num x; // Declare instance variable x, initially null.
  num y; // Declare y, initially null.
  num z = 0; // Declare z, initially 0.
}

所有未初始化的實例變量的值都為null辉词。
所有實例變量都會生成隱式getter方法。非最終實例變量也生成隱式setter方法猾骡。

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.
}

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

通過創(chuàng)建與類同名的函數(shù)來聲明構(gòu)造函數(shù)(另外瑞躺,還可以選擇添加一個在命名構(gòu)造函數(shù)中描述的附加標(biāo)識符)敷搪。最常見的構(gòu)造函數(shù)形式是生成構(gòu)造函數(shù),它創(chuàng)建一個類的新實例:

class Point {
  num x, y;

  Point(num x, num y) {
    // There's a better way to do this, stay tuned.
    this.x = x;
    this.y = y;
  }
}

this關(guān)鍵字引用當(dāng)前實例幢哨。
注意:只有在名稱沖突時才使用“this”赡勘。否則Dart會忽略this
將構(gòu)造函數(shù)參數(shù)賦給實例變量的模式非常常見,Dart采用了語法上的通用性來簡化操作:

class Point {
  num x, y;

  // Syntactic sugar for setting x and y
  // before the constructor body runs.
  Point(this.x, this.y);
}

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

如果不聲明構(gòu)造函數(shù)捞镰,則提供一個默認(rèn)構(gòu)造函數(shù)狮含。默認(rèn)構(gòu)造函數(shù)沒有參數(shù),并調(diào)用超類中的無參數(shù)構(gòu)造函數(shù)曼振。

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

子類不會從父類繼承構(gòu)造函數(shù)几迄。聲明沒有構(gòu)造函數(shù)的子類只有默認(rèn)的構(gòu)造函數(shù)(沒有參數(shù),沒有名稱)冰评。

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

使用聲明構(gòu)造函數(shù)實現(xiàn)一個類的多個構(gòu)造函數(shù)會更加清晰:

class Point {
  num x, y;

  Point(this.x, this.y);

  // Named constructor
  Point.origin() {
    x = 0;
    y = 0;
  }
}

記住構(gòu)造函數(shù)不是繼承的映胁,這意味著父類的命名構(gòu)造函數(shù)不是由子類繼承的。如果您希望使用在超類中定義的聲明構(gòu)造函數(shù)來創(chuàng)建子類甲雅,則必須在子類中實現(xiàn)該構(gòu)造函數(shù)解孙。

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

默認(rèn)情況下,子類中的構(gòu)造函數(shù)調(diào)用超類的未命名的無參數(shù)構(gòu)造函數(shù)抛人。在構(gòu)造函數(shù)主體的開頭調(diào)用父類的構(gòu)造函數(shù)弛姜。如果還使用初始化器列表,則在調(diào)用超類之前執(zhí)行妖枚。綜上所述廷臼,執(zhí)行順序如下:

  1. 初始化器列表
  2. 父類的無參數(shù)構(gòu)造函數(shù)
  3. main類的無參數(shù)構(gòu)造函數(shù)

如果超類沒有未命名的、無參數(shù)的構(gòu)造函數(shù)绝页,那么您必須手動調(diào)用超類中的一個構(gòu)造函數(shù)荠商。在構(gòu)造函數(shù)主體(如果有的話)之前的冒號(:)后面指定父類構(gòu)造函數(shù)。

初始化器列表

除了調(diào)用父類構(gòu)造函數(shù)外续誉,您還可以在構(gòu)造函數(shù)主體運行之前初始化實例變量莱没。用逗號分隔初始化器。

// Initializer list sets instance variables before
// the constructor body runs.
Point.fromJson(Map<String, num> json)
    : x = json['x'],
      y = json['y'] {
  print('In Point.fromJson(): ($x, $y)');
}

初始化器的不能用this訪問
在開發(fā)期間酷鸦,您可以通過在初始化器列表中使用assert來驗證輸入饰躲。

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

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

有時,構(gòu)造函數(shù)的唯一目的是重定向到同一類中的另一個構(gòu)造函數(shù)臼隔。重定向構(gòu)造函數(shù)的主體是空的嘹裂,構(gòu)造函數(shù)調(diào)用出現(xiàn)在冒號(:)之后。

class Point {
  num x, y;

  // The main constructor for this class.
  Point(this.x, this.y);

  // Delegates to the main constructor.
  Point.alongXAxis(num x) : this(x, 0);
}

恒定的構(gòu)造函數(shù)

如果您的類生成的對象永遠(yuǎn)不會改變躬翁,那么您可以使這些對象成為編譯時常量焦蘑。為此盯拱,定義一個const構(gòu)造函數(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ù)

在實現(xiàn)構(gòu)造函數(shù)時使用factory關(guān)鍵字例嘱,該構(gòu)造函數(shù)并不總是創(chuàng)建其類的新實例。例如宁舰,工廠構(gòu)造函數(shù)可能從緩存返回實例拼卵,或者返回子類型的實例。
下面的示例演示從緩存返回對象的工廠構(gòu)造函數(shù):

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) {
    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”蛮艰。
像調(diào)用任何其他構(gòu)造函數(shù)一樣調(diào)用工廠構(gòu)造函數(shù):

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

方法

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

實例方法

對象上的實例方法可以訪問實例變量等等。下面示例中的distanceTo()方法是一個實例方法的示例:

import 'dart:math';

class Point {
  num x, y;

  Point(this.x, this.y);

  num distanceTo(Point other) {
    var dx = x - other.x;
    var dy = y - other.y;
    return sqrt(dx * dx + dy * dy);
  }
}

Getters 和 setters

getter和setter是對對象屬性提供讀寫訪問的特殊方法壤蚜〖垂眩回想一下,每個實例變量都有一個隱式getter袜刷,如果合適的話聪富,還有一個setter。您可以通過實現(xiàn)getter和setter來創(chuàng)建額外的屬性著蟹,使用get和set關(guān)鍵字:

class Rectangle {
  num left, top, width, height;

  Rectangle(this.left, this.top, this.width, this.height);

  // Define two calculated properties: right and bottom.
  num get right => left + width;
  set right(num value) => left = value - width;
  num get bottom => top + height;
  set bottom(num value) => top = value - height;
}

void main() {
  var rect = Rectangle(3, 4, 20, 15);
  assert(rect.left == 3);
  rect.right = 12;
  assert(rect.left == -8);
}

有了getter和setter墩蔓,您可以從實例變量開始,然后用方法包裝它們萧豆,而無需更改客戶機代碼奸披。
注意:諸如increment(++)這樣的操作符以預(yù)期的方式工作,不管getter是否顯式定義涮雷。為了避免任何意外的副作用阵面,操作符只調(diào)用一次getter,將它的值保存在一個臨時變量中洪鸭。

抽象方法

實例膜钓、getter和setter方法可以是抽象的,定義一個接口卿嘲,但將其實現(xiàn)留給其他類颂斜。抽象方法只能存在于抽象類中。

abstract class Doer {
  // Define instance variables and methods...

  void doSomething(); // Define an abstract method.
}

class EffectiveDoer extends Doer {
  void doSomething() {
    // Provide an implementation, so the method is not abstract here...
  }
}

使用 abstract 修飾符定義無法實例化的抽象類a類拾枣。抽象類對于定義接口非常有用沃疮,通常帶有一些實現(xiàn)。如果您希望您的抽象類看起來是可實例化的梅肤,請定義一個工廠構(gòu)造函數(shù)司蔬。
抽象類通常有抽象方法。下面是一個聲明具有抽象方法的抽象類的例子:

abstract class AbstractContainer {
  // Define constructors, fields, methods...

  void updateChildren(); // Abstract method.
}

隱式接口

每個類隱式地定義一個接口姨蝴,其中包含類的所有實例成員及其實現(xiàn)的任何接口俊啼。如果您想要創(chuàng)建一個A類 支持類B 的API ,而不繼承B左医,那么類a應(yīng)該實現(xiàn)B接口授帕。

// A person. The implicit interface contains greet().
class Person {
  // In the interface, but visible only in this library.
  final _name;

  // Not in the interface, since this is a constructor.
  Person(this._name);

  // In the interface.
  String greet(String who) => 'Hello, $who. I am $_name.';
}

// An implementation of the Person interface.
class Impostor implements Person {
  get _name => '';

  String greet(String who) => 'Hi $who. Do you know who I am?';
}

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

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

下面是一個指定一個類實現(xiàn)多個接口的例子

class Point implements Comparable, Location {...}

擴展一個類

使用extend來創(chuàng)建子類同木,使用super來引用超類:

class Television {
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }
  // ···
}

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

重寫成員變量

子類可以覆蓋實例方法、getter和setter跛十。您可以使用@override注釋指示您有意重寫一個成員:

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

要在類型安全的代碼中縮小方法參數(shù)或?qū)嵗兞康念愋屯罚梢允褂谩?a target="_blank" rel="nofollow">covariant”關(guān)鍵字。

重寫運算符

可以覆蓋下表中顯示的操作符芥映。例如洲尊,如果您定義了一個向量類,您可以定義一個+方法來添加兩個向量奈偏。

 <  +   |   []
>   /   ^   []=
<=  ~/  &   ~
>=  *   <<  ==
-   %   >>  

下面是一個類的例子坞嘀,它覆蓋了+和-運算符:

class Vector {
  final int x, y;

  Vector(this.x, this.y);

  Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
  Vector operator -(Vector v) => Vector(x - v.x, y - v.y);

  // Operator == and hashCode not shown. For details, see note below.
  // ···
}

void main() {
  final v = Vector(2, 3);
  final w = Vector(2, 2);

  assert(v + w == Vector(4, 5));
  assert(v - w == Vector(0, 1));
}

如果你重寫==,你也應(yīng)該重寫對象的hashCode getter

noSuchMethod()

如果代碼試圖使用不存在的方法或?qū)嵗兞烤矗梢愿采wnoSuchMethod():

class A {
  // Unless you override noSuchMethod, using a
  // non-existent member results in a NoSuchMethodError.
  @override
  void noSuchMethod(Invocation invocation) {
    print('You tried to use a non-existent member: ' +
        '${invocation.memberName}');
  }
}

你不能調(diào)用一個未實現(xiàn)的方法姆吭,除非下列之一是true:

  • 接收者擁有靜態(tài)類型dynamic
  • 接收方有一個定義未實現(xiàn)方法的靜態(tài)類型(抽象是可以的),而接收方的動態(tài)類型有一個noSuchMethod()的實現(xiàn)唁盏,與類對象中的實現(xiàn)不同内狸。

枚舉類型

枚舉類型,通常稱為枚舉或枚舉厘擂,是一種特殊的類昆淡,用于表示固定數(shù)量的常量值。

使用枚舉

使用enum關(guān)鍵字聲明枚舉類型:

enum Color { red, green, blue }

枚舉中的每個值都有一個索引getter刽严,它返回枚舉聲明中值的從零開始的位置昂灵。例如,第一個值的索引值為0舞萄,第二個值的索引值為1眨补。

assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);

要獲得枚舉中的所有值的列表,請使用枚舉值常量倒脓。

List<Color> colors = Color.values;
assert(colors[2] == Color.blue);

您可以在switch語句中使用枚舉撑螺,如果不處理enum的所有值,就會收到警告:

var aColor = Color.blue;

switch (aColor) {
  case Color.red:
    print('Red as roses!');
    break;
  case Color.green:
    print('Green as grass!');
    break;
  default: // Without this, you see a WARNING.
    print(aColor); // 'Color.blue'
}

枚舉類型有以下限制:

  • 您不能子類化崎弃、混入或?qū)崿F(xiàn)枚舉甘晤。
  • 您不能顯式地實例化枚舉。

向類添加特性:mixins

mixins是在多個類層次結(jié)構(gòu)中重用類代碼的一種方法饲做。
要使用mixin线婚,請使用后跟一個或多個mixin名稱的with關(guān)鍵字。下面的例子展示了兩個使用mixin的類:

  // ···
}

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

要實現(xiàn)mixin盆均,創(chuàng)建一個擴展對象的類塞弊,聲明沒有構(gòu)造函數(shù),也沒有對super的調(diào)用。例如:

bstract class 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');
    }
  }
}

類變量和方法

使用static關(guān)鍵字實現(xiàn)類范圍的變量和方法游沿。
靜態(tài)變量
靜態(tài)變量(類變量)對于類范圍的狀態(tài)和常量非常有用:

class Queue {
  static const initialCapacity = 16;
  // ···
}

void main() {
  assert(Queue.initialCapacity == 16);
}

靜態(tài)變量在使用之前不會初始化饰抒。
靜態(tài)方法
靜態(tài)方法(類方法)不操作實例,因此不能訪問它奏候。例如:

import 'dart:math';

class Point {
  num x, y;
  Point(this.x, this.y);

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

注意:考慮使用通用或廣泛使用的實用程序和功能的頂級函數(shù),而不是靜態(tài)方法唇敞。
可以使用靜態(tài)方法作為編譯時常量蔗草。例如,您可以將靜態(tài)方法作為參數(shù)傳遞給常量構(gòu)造函數(shù)疆柔。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末咒精,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子旷档,更是在濱河造成了極大的恐慌模叙,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鞋屈,死亡現(xiàn)場離奇詭異范咨,居然都是意外死亡,警方通過查閱死者的電腦和手機厂庇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進(jìn)店門渠啊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人权旷,你說我怎么就攤上這事替蛉。” “怎么了拄氯?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵躲查,是天一觀的道長。 經(jīng)常有香客問我译柏,道長鄙麦,這世上最難降的妖魔是什么黔衡? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任盟劫,我火速辦了婚禮,結(jié)果婚禮上塘装,老公的妹妹穿的比我還像新娘蹦肴。我一直安慰自己阴幌,他們只是感情好矛双,可當(dāng)我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布议忽。 她就那樣靜靜地躺著,像睡著了一般愤估。 火紅的嫁衣襯著肌膚如雪速址。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天震捣,我揣著相機與錄音闹炉,去河邊找鬼。 笑死羡棵,一個胖子當(dāng)著我的面吹牛皂冰,可吹牛的內(nèi)容都是我干的秃流。 我是一名探鬼主播舶胀,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼糖赔,長吁一口氣:“原來是場噩夢啊……” “哼轩端!你這毒婦竟也來了基茵?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤声怔,失蹤者是張志新(化名)和其女友劉穎态贤,沒想到半個月后舱呻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體悠汽,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年柿冲,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片假抄。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡怎栽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出宿饱,到底是詐尸還是另有隱情,我是刑警寧澤谬以,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布邮丰,位于F島的核電站,受9級特大地震影響炕檩,放射性物質(zhì)發(fā)生泄漏妈经。R本人自食惡果不足惜骤星,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望爆哑。 院中可真熱鬧洞难,春花似錦、人聲如沸揭朝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽潭袱。三九已至柱嫌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間屯换,已是汗流浹背编丘。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留彤悔,地道東北人嘉抓。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像晕窑,于是被迫代替她去往敵國和親抑片。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內(nèi)容