Dart 語言系列
前言
這一章不按照官方文檔的順序來沫屡,先介紹一下 Classes
.
Classes
Dart
是一個面向?qū)ο蟮恼Z言抒巢,具有類和 mixin-based
繼承。 每一個對象都是一個類的實例茬贵,所有的類都是繼承自 Object
. Mixin-based
繼承意味著雖然每一個類有一個超類(除了對象)蟹漓,但是類體可以在多個類的層次結(jié)構(gòu)中重用运准。
Using class members
對象包含函數(shù)和數(shù)據(jù)成員變量(分別包含方法和實例變量).
當(dāng)你調(diào)用一個方法铭乾,你可以調(diào)用一個對象:這個方法可以訪問對象的方法和數(shù)據(jù)吉拳。
使用 點.
可以引用實例變量的或者方法:
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));
使用 ?.
代替 .
來避免奔潰當(dāng)左邊操作數(shù)是 null
:
// If p is non-null, set its y value to 4.
p?.y = 4;
Using constructors
你可以使用構(gòu)造函數(shù)創(chuàng)建一個對象豺撑。構(gòu)造名字要么是 ClassName
或者 ClassName.identifier
.例如:下面的代碼創(chuàng)建 Point
對象 使用 Point()
和 Point.fromJson()
構(gòu)造函數(shù):
var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});
下面的代碼有同樣的功能烈疚,但是使用可選的 new
關(guān)鍵字在構(gòu)造名字之前:
var p1 = new Point(2, 2);
var p2 = new Point.fromJson({'x': 1, 'y': 2});
Version note: new
關(guān)鍵字在 Dart 2
中變成可選的.
一些類提供了 constant constructors. 使用常量構(gòu)造來創(chuàng)建一個編譯期常量,在構(gòu)造函數(shù)名字前面設(shè)置關(guān)鍵字 const
:
var p = const ImmutablePoint(2, 2);
創(chuàng)建兩個相同的編譯期常量會產(chǎn)生一個單獨聪轿,標(biāo)準(zhǔn)的常量:
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);
assert(identical(a, b)); // They are the same instance!
(注:這個跟一般編程語言中的單例的實現(xiàn)類似爷肝,不過需要注意的一點是:獲取該對象的時候前面需要加上const
。)
在一個常量的上下文陆错,你可以省略const
在一個構(gòu)造函數(shù)或者文字之前灯抛。例如:看這個代碼,創(chuàng)建了一個const
map:
// Lots of const keywords here.
const pointAndLine = const {
'point': const [const ImmutablePoint(0, 0)],
'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};
你可以省略除了第一個使用的const
關(guān)鍵字之外的所有的:
// Only one const, which establishes the constant context.
const pointAndLine = {
'point': [ImmutablePoint(0, 0)],
'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};
如果常量構(gòu)造函數(shù)在常量上下文之外并且在沒有const它的情況下調(diào)用, 它會創(chuàng)建一個 非常量對象(non-constant object
):
var a = const ImmutablePoint(1, 1); // Creates a constant
var b = ImmutablePoint(1, 1); // Does NOT create a constant
assert(!identical(a, b)); // NOT the same instance!
Version note: const
關(guān)鍵愛你變成可選的在常量上下文中 在Dart 2
.
Getting an object’s type (獲取一個對象的類型)
在運行時為了獲取到一個類型音瓷,你可以使用對象的 runtimeType
屬性对嚼,將會返回 Type 對象.
print('The type of a is ${a.runtimeType}');
到這里你已經(jīng)明白如何使用 classes
. 剩余的部分展示如何實現(xiàn)classes
.
Instance variables
這里是如何聲明一個實例變量:
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
方法。Non-final
實例變量也會生成一個隱式的 setter
方法绳慎。更多細(xì)節(jié)纵竖,查看 Getters and 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.
}
如果初始化聲明它的實例變量(而不是構(gòu)造函數(shù)或方法)杏愤,則在創(chuàng)建實例時該變量會被賦值靡砌,是在該實例在構(gòu)造函數(shù)及其初始化列表執(zhí)行之前忿磅。
Constructors
聲明一個構(gòu)造器通過創(chuàng)建一個和類名相同的函數(shù)(另外柬采,還有一個額外的標(biāo)識符被描述在 Named constructors. 最通常的構(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)前實例。
**Note:使用 this
僅僅在在有命名沖突的時汇恤。相反索守,Dart
省略了 this
**
賦值構(gòu)造器參數(shù)給實例變量的形式是相同的胜茧,Dart
有語法糖使初始化更加簡單:
class Point {
num x, y;
// Syntactic sugar for setting x and y
// before the constructor body runs.
Point(this.x, this.y);
}
Default constructors
如果你沒有生成一個構(gòu)造函數(shù)媳瞪,將會提供一個默認(rèn)的構(gòu)造函數(shù)。默認(rèn)的構(gòu)造函數(shù)沒有參數(shù)并且在超類中會觸發(fā)無參的構(gòu)造函數(shù)照宝。
Constructors aren’t inherited (構(gòu)造函數(shù)不能繼承)
子類是不能夠繼承構(gòu)造函數(shù)從它的超類中蛇受。沒有聲明構(gòu)造函數(shù)的子類擁有默認(rèn)(無參,沒有名字)構(gòu)造器厕鹃。
Named constructors
使用命名的構(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ù)不能夠被子類繼承。如果你想要子類被創(chuàng)建的時候擁有一個定義在超類的命名構(gòu)造函數(shù)把将,你必須一個在子類的構(gòu)造函數(shù)轻专。
Invoking a non-default superclass constructor
默認(rèn)的,子類的構(gòu)造函數(shù)會調(diào)用超類中無名的察蹲,沒有參數(shù)的構(gòu)造函數(shù)请垛。
超類的構(gòu)造函數(shù)會被在構(gòu)造體開始之前被調(diào)用。如果使用了初始化列表(initializer list洽议,在超類被調(diào)用之前會被執(zhí)行宗收。通常,執(zhí)行順序如下;
- 1.initializer list
- 2.superclass’s no-arg constructor
- 3.main class’s no-arg constructor
如果超類沒有無名亚兄,無參的構(gòu)造函數(shù)混稽,你必須手動的去調(diào)用超類中一個構(gòu)造函數(shù)。在冒號(:)之后审胚,在構(gòu)造函數(shù)體(如果有)之前指定超類構(gòu)造函數(shù)匈勋。
在下面的例子,Employee
類調(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
}
因為在調(diào)用構(gòu)造函數(shù)之前會計算超類構(gòu)造函數(shù)的參數(shù)膳叨,所以參數(shù)可以是一個表達(dá)式洽洁,例如函數(shù)調(diào)用:
class Employee extends Person {
Employee() : super.fromJson(getDefaultData());
// ···
}
** Warning:超類的構(gòu)造函數(shù)的參數(shù)是不能夠訪問 this
.例如: 參數(shù)可以被靜態(tài)方法調(diào)用但是不能是實例方法.**
Initializer list
除了調(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)');
}
Warning:初始化函數(shù)的右側(cè)是不能夠訪問 this
.
在開發(fā)中,你可以使用 assert
來驗證輸入的正確性 在初始化列表中.
Point.withAssert(this.x, this.y) : assert(x >= 0) {
print('In Point.withAssert(): ($x, $y)');
}
當(dāng)設(shè)置 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(3, 4);
print(p.distanceFromOrigin);
}
//prints 5
Redirecting constructors
有時候構(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);
}
Constant constructors
如果你有的類所創(chuàng)建的對象永遠(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ù)不是總是創(chuàng)建常量.
Factory constructors
使用 factory
關(guān)鍵字 當(dāng)實現(xiàn)一個構(gòu)造函數(shù),這個構(gòu)造函數(shù)不是總創(chuàng)建新的實例跟磨。例如间聊,工廠構(gòu)造函數(shù)構(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);
}
}
NOTE:工廠構(gòu)造函數(shù)不能訪問 this
.
調(diào)用一個工廠構(gòu)造函數(shù)和你調(diào)用其他構(gòu)造函數(shù)一樣:
var logger = Logger("UI");
logger.log('Button click');
Methods
Methods
是為對象提供行為的函數(shù)哎榴。
Instance methods
對象的實例方法可以訪問實例變量和this
. 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 and setters
Getters
和 setters
為對象提供了讀和寫屬性.每一個實例都包含一個隱式的getter
和 setter
. 如果通過實現(xiàn) getters
和 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);
}
使用 getters
和 setters
,你可以從實例變量開始,然后使用方法包裹它們尚蝌,這一切是不需要改變客戶端代碼的迎变。
Note:無論getter
是否明確定義,例如自增操作符(++)會如期運行飘言。為了避免不可以預(yù)料的副作用衣形,操作符只需調(diào)用一次getter,將其值保存在臨時變量中
Abstract methods
實例姿鸿,getter
和 setter
方法可以抽象谆吴,定義一個接口而把實現(xiàn)留給其他類。抽象方法僅僅存在 abstract classes般妙。
為了使方法抽象纪铺,使用分號(;) 來代替方法體:
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 classes
使用 abstract
修飾符來定義一個抽象類- 這個類是不可以被初始化的。抽象類對于定義接口是有用的碟渺,通常還有一些實現(xiàn)鲜锚。如果你想你的抽象類可以被實例化,定義一個 factory constructor.
抽象類通常擁有 abstract methods苫拍。這里是抽象類包含抽象方法的例子:
// This class is declared abstract and thus
// can't be instantiated.
abstract class AbstractContainer {
// Define constructors, fields, methods...
void updateChildren(); // Abstract method.
}
Implicit interfaces
每個類都隱式定義一個接口芜繁,該接口包含該類的所有實例成員及其實現(xiàn)的任何接口.你如果想要創(chuàng)建的類A支持類B的API但是并沒有繼承B類的實現(xiàn),類A應(yīng)該實現(xiàn)類B的接口绒极。
一個類實現(xiàn)一個或者多個接口統(tǒng)統(tǒng)聲明它們在 implements
條款中骏令,并且提供接口所需要的APIs.例如:
// 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 {...}
Extending a class
使用 extends
來創(chuàng)建一個子類,并且super
來引用 超類:
class Television {
void turnOn() {
_illuminateDisplay();
_activateIrSensor();
}
// ···
}
class SmartTelevision extends Television {
void turnOn() {
super.turnOn();
_bootNetworkInterface();
_initializeMemory();
_upgradeApps();
}
// ···
}
Overriding members
子類可以覆蓋實例方法垄提,getter
,setter
. 你可以使用@override
標(biāo)注只能你想要覆蓋的成員:
class SmartTelevision extends Television {
@override
void turnOn() {...}
// ···
}
為了抽象一個方法參數(shù)的類型或者實例變量在代碼中是 type safe ,你可以使用
covariant
keyword.
Overridable operators
你可以覆蓋下面表中的運算符. 例如 : 定義 Vector
類榔袋,你可能定義一個 +
方法來相加兩個vectors.
Note:你可能要注意 !=
是不能覆蓋的操作符. 表達(dá)式e1 != e2
僅僅是 !(e1 == e2)
的語法糖.
這里有一個覆蓋 +
和 -
操作符的例子:
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 方法. 覆蓋 ==
和 hasCode
,可以參考 Implementing map keys。
noSuchMethod()
為了檢測或者響應(yīng)代碼是否企圖使用不存在的方法或者實體變量铡俐,你可以重寫 noSuchMethod()
:
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)的方法除非下列中的一個條件成立:
- 接收者擁有一個靜態(tài)類型
dynamic
- 接收者擁有一個靜態(tài)類型凰兑,這個類型定義了未實現(xiàn)的方法(abstract 是可以的),并且接收者的動態(tài)類型實現(xiàn)了
noSuchMethod
,這不同于類中的對象.
更多信息审丘,可以參照非正式的 noSuchMethod forwarding specification.
Enumerated types
枚舉類型吏够,常常被稱作 enumerations
或者 enums
,是一種特殊類型的類,用來表示固定數(shù)量的常量值.
Using enums
聲明一個枚舉類型使用 enum
關(guān)鍵字:
enum Color { red, green, blue }
每一個值在枚舉中擁有一個 index
getter
, 將會返回一個基于零位置的值在枚舉聲明中. 例如: 第一個值索引是 0,第二值的索引是 1.
assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);
為了得到枚舉中所有的值的列表滩报,使用枚舉的 values
常量.
List<Color> colors = Color.values;
assert(colors[2] == Color.blue);
你可以在 switch
語句中使用枚舉, 如果你沒有處理枚舉的值將會得到警告:
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)枚舉。
- 您無法顯式實例化枚舉脓钾。
更多信息售睹,參考 Dart Language Specification.
Adding features to a class: mixins
Mixins
可以多個類的層級中重復(fù)的使用類的代碼.
要使用 mixin
,使用 wtih
關(guān)鍵字后面跟上一個或者多個mixin
名字.
下面的例子展示了兩個類使用 minxins
:
class Musician extends Performer with Musical {
// ···
}
class Maestro extends Person
with Musical, Aggressive, Demented {
Maestro(String maestroName) {
name = maestroName;
canConduct = true;
}
}
要實現(xiàn) mixin
, 創(chuàng)建一個類繼承 Object
并且聲明沒有構(gòu)造函數(shù)可训。除非你想要你的 mixin
當(dāng)做一個標(biāo)準(zhǔn)類可以使用昌妹,要不使用 mixin
關(guān)鍵字來代替 class
生真。例如:
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)可以使用 minxin
- 例如,你的minxin
可以調(diào)用未定義的方法 - 使用 on
來確定所必須的超類:
mixin MusicalPerformer on Musician {
// ···
}
Version note: minxin
關(guān)鍵字是在Dart 2.1 代碼中被引入的捺宗,在早期通常使用 abstract class
代替。
注:關(guān)于minxin
更詳細(xì)的介紹和使用川蒙,可以參照這篇文章
Class variables and methods
使用 static
關(guān)鍵字來實現(xiàn)類范圍的變量和方法.
Static variables
靜態(tài)變量(類變量) 對于類范圍的狀態(tài)和常量是有用的:
class Queue {
static const initialCapacity = 16;
// ···
}
void main() {
assert(Queue.initialCapacity == 16);
}
靜態(tài)變量直到他們被使用的時候才會初始化.
Static methods
靜態(tài)方法(類方法)不能操作一個實例變量蚜厉,并且不能夠訪問 this
。例如:
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);
}
你可以使用靜態(tài)方法當(dāng)做編譯期常量.例如畜眨,你可以傳遞一個靜態(tài)方法當(dāng)做一個參數(shù)給一個常量構(gòu)造函數(shù)昼牛。