這節(jié)詳細介紹如何定義類的變量聲明和構造方法挂洛。
實例變量
下面是聲明實例變量的示例:
class Point {
num x; // 聲明實例變量 x 并初始化為 null。
num y; // 聲明實例變量 y 并初始化為 null。
num z = 0; // 聲明實例變量 z 并初始化為 0卜朗。
}
備忘:"所有未初始化的實例變量其值均為 null熬的。"
所有實例變量均會隱式地聲明一個 Getter 方法,非 final 類型的實例變量還會隱式地聲明一個 Setter 方法拌喉。你可以查閱 Getter 和 Setter 速那。
class Point {
num x;
num y;
}
void main() {
var point = Point();
point.x = 4; // 使用 x 的 Setter 方法。
assert(point.x == 4); // 使用 x 的 Getter 方法尿背。
assert(point.y == null); // 默認值為 null端仰。
}
備忘:
如果你在聲明一個實例變量的時候就將其初始化(而不是在構造函數或其它方法中),那么該實例變量的值就會在對象實例創(chuàng)建的時候被設置田藐,該過程會在構造函數以及它的初始化器列表執(zhí)行前荔烧。
構造函數
聲明一個與類名一樣的函數即可聲明一個構造函數(對于命名式構造函數還可以添加額外的標識符)。大部分的構造函數形式是生成式構造函數汽久,其用于創(chuàng)建一個類的實例:
class Point {
num x, y;
Point(num x, num y) {
// 還會有更好的方式來實現此邏輯鹤竭,敬請期待。
this.x = x;
this.y = y;
}
}
當且僅當命名沖突時使用 this 關鍵字才有意義景醇,否則 Dart 會忽略 this 關鍵字臀稚。
對于大多數編程語言來說在構造函數中為實例變量賦值的過程都是類似的,而 Dart 則提供了一種特殊的語法糖來簡化該步驟:
class Point {
num x, y;
// 在構造函數體執(zhí)行前用于設置 x 和 y 的語法糖三痰。
Point(this.x, this.y);
}
這種用類名加變量的聲明構造方法只能有一個吧寺,那么如何定義多個構造方法呢?先帶著疑問散劫,繼續(xù)往下看稚机。
默認構造函數
如果你沒有聲明構造函數,那么 Dart 會自動生成一個無參數的構造函數并且該構造函數會調用其父類的無參數構造方法获搏。
構造函數不會被繼承
子類不會繼承父類的構造函數赖条,如果子類沒有聲明構造函數,那么只會有一個默認無參數的構造函數颜凯。
命名式構造函數
可以為一個類聲明多個命名式構造函數來表達更明確的意圖:
class Point {
num x, y;
Point(this.x, this.y);
// 命名式構造函數
Point.origin() {
x = 0;
y = 0;
}
}
跟構造函數的語法糖一樣谋币,也可以寫成:
class Point {
num x, y;
Point(this.x, this.y);
// 命名式構造函數
Point.origin(this.x, this.y) ;
}
備忘:
- 記住構造函數是不能被繼承的,這將意味著子類不能繼承父類的命名式構造函數症概,如果你想在子類中提供一個與父類命名構造函數名字一樣的命名構造函數蕾额,則需要在子類中顯式地聲明。
- 如果聲明有參構造函數彼城,這時就沒有無參構造函數诅蝶,也無法顯示聲明退个,因為類型構造函數只有一個;如果你聲明了命名式的構造函數调炬,這時就沒有無參構造函數了语盈,但你可以顯示地聲明出來。
- 第1點不能函數名重名的原因是:C++ 與 Java 的做法是缰泡,提供函數的重載刀荒,即提供同名但參數不同的函數。但 Dart 認為重載會導致混亂棘钞,因此從設計之初就
不支持重載
缠借,而是提供了可選命名參數和可選參數。
- 第1點不能函數名重名的原因是:C++ 與 Java 的做法是缰泡,提供函數的重載刀荒,即提供同名但參數不同的函數。但 Dart 認為重載會導致混亂棘钞,因此從設計之初就
調用父類非默認構造函數
默認情況下宜猜,子類的構造函數會調用父類的匿名無參數構造方法泼返,并且該調用會在子類構造函數的函數體代碼執(zhí)行前,如果子類構造函數還有一個初始化列表姨拥,那么該初始化列表會在調用父類的該構造函數之前被執(zhí)行绅喉,總的來說,這三者的調用順序如下:
- 初始化列表
- 父類的無參數構造函數
- 當前類的構造函數
如果父類沒有匿名無參數構造函數叫乌,那么子類必須調用父類的其中一個構造函數柴罐,為子類的構造函數指定一個父類的構造函數只需在構造函數體前使用(:)指定。
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';
}
備忘:
"父類沒有匿名無參數構造函數综芥,那么子類必須
調用父類的其中一個構造函數"
因為參數會在子類構造函數被執(zhí)行前傳遞給父類的構造函數丽蝎,因此該參數也可以是一個表達式,比如一個函數:
class Employee extends Person {
Employee() : super.fromJson(defaultData);
// ···
}
class Employee extends Person {
Employee() : super();
// ···
}
備忘:
- 構造函數聲明主體后面接
:
可以調用父類的構造方法膀藐,用super代替父類的類型屠阻,如super()
、super.fromJson()
额各,用this可以接本類的其他構造方法国觉,很常用很強大,要記住虾啦。
- 構造函數聲明主體后面接
- 傳遞給父類構造函數的參數不能使用
this
關鍵字麻诀,因為在參數傳遞的這一步驟,子類構造函數尚未執(zhí)行傲醉,子類的實例對象也就還未初始化蝇闭,因此所有的實例成員
都不能被訪問,但是類成員
可以硬毕。
- 傳遞給父類構造函數的參數不能使用
- 構造函數聲明主體后面接
:
不僅僅是接父類構造函數呻引,還可以接表達式、初始化列表(下面會介紹)吐咳。
- 構造函數聲明主體后面接
初始化列表
剛才我們提到初始化列表會在父類構造函數前執(zhí)行逻悠,到底什么是初始化列表元践?
//初始化列表必須在父類表達式聲明之前
Employee.fromJson(Map data) : employeeName = 'hahha',super(){
print('in Employee');
}
除了調用父類構造函數之外,還可以在構造函數體執(zhí)行之前初始化實例變量童谒,每個實例變量之間使用逗號分隔单旁,這就是初始化列表:
// 使用初始化列表在構造函數體執(zhí)行前設置實例變量。
Point.fromJson(Map<String, num> json)
: x = json['x'],
y = json['y'] {
print('In Point.fromJson(): ($x, $y)');
}
初始化列表表達式 = 右邊的語句不能使用 this 關鍵字饥伊。
初始化列表用來設置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);
}
重定向構造函數
有時候類中的構造函數會調用類中其它的構造函數撵渡,該重定向構造函數沒有函數體融柬,只需在函數簽名后使用(:)指定需要重定向到的其它構造函數即可:
class Point {
num x, y;
// 該類的主構造函數。
Point(this.x, this.y);
// 委托實現給主構造函數趋距。
Point.alongXAxis(num x) : this(x, 0);
}
這個語法糖和命名構造函數共同實現多個構造函數的實現,比起只用命名構造函數實現越除,委托實現可以幫忙實現重復的初始化代碼节腐。
委托后面不能再加大括號:
Employee.fromJson(Map data) : this();
常量構造函數
如果類生成的對象都是不會變的,那么可以在生成這些對象時就將其變?yōu)榫幾g時常量摘盆。你可以在類的構造函數前加上const
關鍵字并確保所有實例變量均為final
來實現該功能翼雀。
class ImmutablePoint {
static final ImmutablePoint origin =
const ImmutablePoint(0, 0);
final num x, y;
const ImmutablePoint(this.x, this.y);
}
工廠構造函數
使用factory
關鍵字標識類的構造函數將會令該構造函數變?yōu)楣S構造函數,這將意味著使用該構造函數構造類的實例時并非總是會返回新的實例對象孩擂。例如狼渊,工廠構造函數可能會從緩存中返回一個實例,或者返回一個子類型的實例类垦。
以下示例演示了從緩存中返回對象的工廠構造函數:
class Logger {
final String name;
bool mute = false;
// _cache 變量是庫私有的狈邑,因為在其名字前面有下劃線。
static final Map<String, Logger> _cache =
<String, Logger>{};
factory Logger(String name) {
return _cache.putIfAbsent(
name, () => Logger._internal(name));
}
Logger._internal(this.name);
void log(String msg) {
if (!mute) print(msg);
}
}
在工廠構造函數中無法訪問 this蚤认。
工廠構造函的調用方式與其他構造函數一樣:
var logger = Logger('UI');
logger.log('Button clicked');