Dart 是一種基于類和 mixin 繼承機制的面向對象的語言。 每個對象都是一個類的實例,所有的類都繼承于 Object. 。 基于 * Mixin 繼承* 意味著每個類(除 Object 外) 都只有一個超類, 一個類中的代碼可以在其他多個繼承類中重復使用。
對象是由函數(shù)和數(shù)據(jù)(即方法和實例變量)組成。 方法的調用要通過對象來完成: 調用的方法可以訪問其對象的其他函數(shù)和數(shù)據(jù)。
使用 (.) 來引用實例對象的變量和方法:
var p = Point(2, 2);
// 為實例的變量 y 設置值。
p.y = 3;
// 獲取變量 y 的值。
assert(p.y == 3);
// 調用 p 的 distanceTo() 方法。
num distance = p.distanceTo(Point(4, 4));
使用 ?. 來代替 . , 可以避免因為左邊對象可能為 null , 導致的異常:
// 如果 p 為 non-null,設置它變量 y 的值為 4。
p?.y = 4;
通過 構造函數(shù) 創(chuàng)建對象。 構造函數(shù)的名字可以是 ClassName 或者 ClassName.identifier。例如, 以下代碼使用 Point 和 Point.fromJson() 構造函數(shù)創(chuàng)建 Point 對象:
var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});
以下代碼具有相同的效果, 但是構造函數(shù)前面的的 new 關鍵字是可選的:
var p1 = new Point(2, 2);
var p2 = new Point.fromJson({'x': 1, 'y': 2});
版本提示: 在 Dart 2 中 new
關鍵字變成了可選的。
一些類提供了常量構造函數(shù)。 使用常量構造函數(shù),在構造函數(shù)名之前加 const 關鍵字,來創(chuàng)建編譯時常量時:
var p = const ImmutablePoint(2, 2);
構造兩個相同的編譯時常量會產生一個唯一的, 標準的實例:
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);
assert(identical(a, b)); // 它們是同一個實例。
在 常量上下文 中, 構造函數(shù)或者字面量前的 const 可以省略。 例如,下面代碼創(chuàng)建了一個 const 類型的 map 對象:
// 這里有很多的 const 關鍵字。
const pointAndLine = const {
'point': const [const ImmutablePoint(0, 0)],
'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};
保留第一個 const 關鍵字,其余的全部省略:
// 僅有一個 const ,由該 const 建立常量上下文。
const pointAndLine = {
'point': [ImmutablePoint(0, 0)],
'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};
如果常量構造函數(shù)在常量上下文之外, 且省略了 const 關鍵字, 此時創(chuàng)建的對象是非常量對象:
var a = const ImmutablePoint(1, 1); // 創(chuàng)建一個常量對象
var b = ImmutablePoint(1, 1); // 創(chuàng)建一個非常量對象
assert(!identical(a, b)); // 兩者不是同一個實例!
版本提示: 在 Dart 2 中,一個常量上下文中的 const
關鍵字可以被省略。
使用對象的 runtimeType 屬性, 可以在運行時獲取對象的類型, runtimeType 屬性回返回一個 Type 對象。
print('The type of a is ${a.runtimeType}');
到目前為止,我們已經(jīng)解了如何_使用_類。 本節(jié)的其余部分將介紹如何_實現(xiàn)_一個類。
下面是聲明實例變量的示例:
class Point {
num x; // 聲明示例變量 x,初始值為 null 。
num y; // 聲明示例變量 y,初始值為 null 。
num z = 0; // 聲明示例變量 z,初始值為 0 。
}
未初始化實例變量的默認人值為 “null” 。
所有實例變量都生成隱式 getter 方法。 非 final 的實例變量同樣會生成隱式 setter 方法。 有關更多信息,參考 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.
}
如果在聲明時進行了示例變量的初始化, 那么初始化值會在示例創(chuàng)建時賦值給變量, 該賦值過程在構造函數(shù)及其初始化列表執(zhí)行之前。
通過創(chuàng)建一個與其類同名的函數(shù)來聲明構造函數(shù) (另外,還可以附加一個額外的可選標識符,如 命名構造函數(shù) 中所述)。 下面通過最常見的構造函數(shù)形式, 即生成構造函數(shù), 創(chuàng)建一個類的實例:
class Point {
num x, y;
Point(num x, num y) {
// 還有更好的方式來實現(xiàn)下面代碼,敬請關注。
this.x = x;
this.y = y;
}
}
使用 this 關鍵字引用當前實例。
提示: 近當存在命名沖突時,使用 this 關鍵字。 否則,按照 Dart 風格應該省略 this 。
通常模式下,會將構造函數(shù)傳入的參數(shù)的值賦值給對應的實例變量, Dart 自身的語法糖精簡了這些代碼:
class Point {
num x, y;
// 在構造函數(shù)體執(zhí)行前,
// 語法糖已經(jīng)設置了變量 x 和 y。
Point(this.x, this.y);
}
在沒有聲明構造函數(shù)的情況下, Dart 會提供一個默認的構造函數(shù)。 默認構造函數(shù)沒有參數(shù)并會調用父類的無參構造函數(shù)。
子類不會繼承父類的構造函數(shù)。 子類不聲明構造函數(shù),那么它就只有默認構造函數(shù) (匿名,沒有參數(shù)) 。
使用命名構造函數(shù)可為一個類實現(xiàn)多個構造函數(shù), 也可以使用命名構造函數(shù)來更清晰的表明函數(shù)意圖:
class Point {
num x, y;
Point(this.x, this.y);
// 命名構造函數(shù)
Point.origin() {
x = 0;
y = 0;
}
}
切記,構造函數(shù)不能夠被繼承, 這意味著父類的命名構造函數(shù)不會被子類繼承。 如果希望使用父類中定義的命名構造函數(shù)創(chuàng)建子類, 就必須在子類中實現(xiàn)該構造函數(shù)。
默認情況下,子類的構造函數(shù)會自動調用父類的默認構造函數(shù)(匿名,無參數(shù))。 父類的構造函數(shù)在子類構造函數(shù)體開始執(zhí)行的位置被調用。 如果提供了一個 initializer list (初始化參數(shù)列表), 則初始化參數(shù)列表在父類構造函數(shù)執(zhí)行之前執(zhí)行。 總之,執(zhí)行順序如下:
如果父類中沒有匿名無參的構造函數(shù), 則需要手工調用父類的其他構造函數(shù)。 在當前構造函數(shù)冒號 (:) 之后,函數(shù)體之前,聲明調用父類構造函數(shù)。
下面的示例中,Employee 類的構造函數(shù)調用了父類 Person 的命名構造函數(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';
}
由于父類的構造函數(shù)參數(shù)在構造函數(shù)執(zhí)行之前執(zhí)行, 所以參數(shù)可以是一個表達式或者一個方法調用:
class Employee extends Person {
Employee() : super.fromJson(getDefaultData());
// ···
}
警告: 調用父類構造函數(shù)的參數(shù)無法訪問 this 。 例如,參數(shù)可以為靜態(tài)函數(shù)但是不能是實例函數(shù)。
除了調用超類構造函數(shù)之外, 還可以在構造函數(shù)體執(zhí)行之前初始化實例變量。 各參數(shù)的初始化用逗號分隔。
// 在構造函數(shù)體執(zhí)行之前,
// 通過初始列表設置實例變量。
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)');
}
使用初始化列表可以很方便的設置 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);
}
有時構造函數(shù)的唯一目的是重定向到同一個類中的另一個構造函數(shù)。 重定向構造函數(shù)的函數(shù)體為空, 構造函數(shù)的調用在冒號 (:) 之后。
class Point {
num x, y;
// 類的主構造函數(shù)。
Point(this.x, this.y);
// 指向主構造函數(shù)
Point.alongXAxis(num x) : this(x, 0);
}
如果該類生成的對象是固定不變的, 那么就可以把這些對象定義為編譯時常量。 為此,需要定義一個 const 構造函數(shù), 并且聲明所有實例變量為 final。
class ImmutablePoint {
static final ImmutablePoint origin =
const ImmutablePoint(0, 0);
final num x, y;
const ImmutablePoint(this.x, this.y);
}
常量構造函數(shù)創(chuàng)建的實例并不總是常量。 更多內容,查看 使用構造函數(shù) 章節(jié)。
當執(zhí)行構造函數(shù)并不總是創(chuàng)建這個類的一個新實例時,則使用 factory 關鍵字。 例如,一個工廠構造函數(shù)可能會返回一個 cache 中的實例, 或者可能返回一個子類的實例。
以下示例演示了從緩存中返回對象的工廠構造函數(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);
}
}
提示: 工廠構造函數(shù)無法訪問 this。
工廠構造函的調用方式與其他構造函數(shù)一樣:
var logger = Logger('UI');
logger.log('Button clicked');
更多建議: