一、Dart 語言
Dart 是一門物件導向的語言。
Dart 語言中所有的變數引用都是一個物件,並且所有的物件都對應一個類的例項。無論是數字、函式和null
,都是一個物件,所有物件繼承自Object
類。在 2.12 版本 Dart 新增空安全後(sound null safety),null
不再是Object
的子類。
Object
Object 具有下列特性:
- 兩個屬性:
hashCode
和runtimeType
; - 兩個方法:
noSuchMethod
和toString
; - 操作符:Operator
hashCode
hashCode 是 read-only 屬性的 int 型別資料,因為 Dart 所有的變數引用均繼承於Object
,所以每個變數引用都有自己的hashCode
。hashCode
的設計目的是為了提高 Dart VM 引擎在解析資料結構時的效率,並且參與判斷物件的邏輯運算,與操作符 operator 相輔相成。考察如下程式碼:
void main() {
P p1 = P("yo","no");
P p2 = P("yo","no");
print(p1 == p2);
print(p1.hashCode);
print(p2.hashCode);
Map<P, String> myMap = {};
myMap[p1] = "first Item";
print(myMap.containsKey(p2));
}
class P {
var naam;
var kaam;
P(this.naam, this.kaam);
@override
int get hashCode {
int result = 11 * naam.hashCode;
result = 13 * (result + kaam.hashCode);
return result;
}
@override
bool operator == (dynamic otherObj) {
return (otherObj is P) && (otherObj.naam == naam) && (otherObj.kaam == kaam);
}
}
複製程式碼
程式碼重寫了P
物件的比較相等的邏輯,從而令p1
和p2
的比較結果返回為true
。但同時需要重寫hashCode
的邏輯,否則返回值仍舊為false
。
Dart 建議hashCode
和Operator
如有必要重寫其中之一,那麼另一個也需要重寫,從而令雜湊對映正常工作,否則Operator
重寫無效。
runtimeType
runtimeType
是 read-only 屬性,表達資料結構在執行時的型別。runtimeType
涉及 Dart VM 執行機制,此處留坑以後再來填。
noSuchMethod
當呼叫物件上不存在的方法時,就會觸發noSuchMethod
,考察如下程式碼:
void main() {
P p = P();
print(p.add(888));
}
class P {
@override
noSuchMethod(Invocation invocation) =>
'Got the ${invocation.memberName} with arguments ${invocation.positionalArguments}';
}
複製程式碼
在 DartPad 編輯器執行上述程式碼,發現並未觸發noSuchMethod
方法, 而是直接丟擲了編譯錯誤。這是因為noSuchMethod
觸發需要有兩個條件:
- 呼叫方必須是
dynamic
型別; - 呼叫方具有被呼叫方法的定義但未實現,同時
noSuchMethod
也被重寫;
所以只有如下兩種情況,才會觸發noSuchMethod
:
void main() {
dynamic p = P();
Q q = Q(); // 該情況下,q 可以是 Q 型別,也可以是 dynamic
print(p.add(123));
print(q.add(123));
}
class P {
@override
noSuchMethod(Invocation invocation) => 'Got the ${invocation.memberName} with arguments ${invocation.positionalArguments}';
}
class Q {
miss(int data);
@override
noSuchMethod(Invocation invocation) => 'Got the ${invocation.memberName} with arguments ${invocation.positionalArguments}';
}
複製程式碼
toString
toString
方法使用字串來表達物件的資訊,也可以在將數字轉換為字串的場景下使用。開發者也可以重寫 toString
方法,以加入自定義內容。
void main() {
P p = P();
p.toString();
}
class P {
@override
toString() {
return 'this is custom text'
}
}
複製程式碼
二、Dart 類與基本方法
1.類的定義和建構函式
class P {
String name;
int age;
P(String dataName, int dataAge) {
name = dataName;
age = dataAge;
}
bool isOld() {
return age > 30 ? true : false;
}
}
void main() {
var p = new P('tom', 12);
print(p.name);
}
複製程式碼
上述程式碼中,宣告瞭P
類,P 中含有兩個屬性:name
和age
。同時也宣告瞭建構函式,通過向建構函式傳入引數從而建立例項p
。
建立例項仍然使用傳統的new
方法,但在 Dart 2 以上版本,new
關鍵字可以省略,同時建構函式也可以用語法糖簡化,程式碼寫法如下:
class P {
String name;
int age;
P(this.name, this.age);
bool isOld() {
return age > 30 ? true : false;
}
}
void main() {
var p = P('tom', 12);
print(p.name); // tom
}
複製程式碼
命名建構函式
除預設的建構函式外,Dart 提供命名建構函式方法。程式碼如下:
class P {
String name;
int age;
P(this.name, this.age);
P.init(String dataName, int dataAge) {
name = dataName;
age = dataAge;
}
bool isOld() {
return age > 30 ? true : false;
}
}
void main() {
var p = P.init('tom', 12);
print(p.name); // tom
}
複製程式碼
命名建構函式的功能看起來與預設建構函式的功能類似,那設計該機制的目的是什麼呢?原因是 Dart 不支援建構函式的過載,無法使用不同的引數來執行構造方法,所以提供命名建構函式的機制來實現多方式建立例項。
工廠建構函式
除了上述兩種建構函式外,Dart 還提供第三種建構函式:工廠建構函式。它的使用場景是:如果呼叫建構函式時,如果例項已存在,不會重新建立例項,而是使用已存在的例項,保證環境內只有一個例項存在,也就是單例模式。考察如下程式碼:
class P {
String name;
static Map<String, dynamic> _cache = {};
factory P(String name) {
if (_cache.containsKey(name)) {
return _cache[name];
} else {
final p = P._internal(name);
_cache[name] = p;
return p;
}
}
P._internal(this.name);
}
void main() {
final p = P('tom');
print(p.name);
}
複製程式碼
工廠建構函式不允許訪問this
,所以_cache
的形態必須是static
。每次建立例項時,如果例項已經在_cache
中存在,那麼返回已存在的例項。換句話說,工廠建構函式並沒有自動建立例項,而是把決定權交給開發者。
2.類的屬性和方法
靜態變數和靜態方法
和大多數語言一樣,Dart 提供靜態變數和靜態方法:
class P {
static age;
static bool isOld() {
return age > 30 ? true : false;
}
}
複製程式碼
靜態方法無法使用this
,也不能訪問非靜態成員,類的例項也無法呼叫靜態方法,並且靜態變數只有在被使用的時候才會初始化。
私有屬性和私有方法
Dart 不提供類似public
、protected
等關鍵字,在變數前新增下劃線即可宣告私有:
class P {
int _age;
P(this._age);
bool _isOld() {
return this._age > 30 ? true : false;
}
bool isOld() {
return _isOld();
}
}
void main() {
final p = P(20);
print(p._age); // error
print(p._isOld()); // error
print(p.isOld()); // false
}
複製程式碼
例項無法直接呼叫私有方法,但是可以通過呼叫公有方法的形式間接呼叫私有方法。
3.類的繼承
建構函式
Dart 通過extends
關鍵字實現繼承,子類繼承父類中公有的屬性和方法,不會繼承建構函式,所以子類的建構函式需通過super
關鍵字來呼叫或改造父類的建構函式:
class P {
num name;
num age;
P(this.name, this.age);
P.xxx(this.name, this.age);
}
// class Q extends P {
// Q(num name, num age): super(name, age);
// }
// class Q extends P {
// num sex;
// Q(num sex, num name, num age): super.xxx(name, age) {
// this.sex = sex;
// }
// }
class Q extends P {
num sex;
Q(num sex, num name, num age): super(name, age) {
this.sex = sex;
}
}
void main() {
final q = Q(12, 13, 14);
print(q.sex); // 12
}
複製程式碼
上述程式碼演示了子類中三種建構函式的表現:
- 直接複用父類建構函式
- 複用和改造父類預設建構函式
- 複用和改造父類命名建構函式
子類呼叫父類方法
如果子類需要呼叫父類方法,同樣使用super
關鍵字,此時方法內部的this
指向子類:
class P {
num name;
num age;
P(this.name, this.age);
bool childCheck() {
return age > 20 ? true : false;
}
}
class Q extends P {
Q(num name, num age): super(name, age);
bool check() {
return super.check();
}
}
void main() {
final a = Q(12, 13);
print(a.childCheck()); // false
}
複製程式碼
子類重寫父類方法
class P {
num name;
num age;
P(this.name, this.age);
bool check() {
return age > 20 ? true : false;
}
}
class Q extends P {
Q(num name, num age): super(name, age);
@override
bool childCheck() {
return age == 13 ? true : false;
}
}
void main() {
final a = Q(12, 13);
print(a.childCheck()); // true
}
複製程式碼
子類在重寫父類方法時,只要方法名稱與父類相同,便可實現邏輯重寫,@override
為可選項,對於複雜邏輯的類功能建議新增@override
。