一,概述
類(Class)是面向對象程序設計,實現信息封裝的基礎。類是一種用戶定義的類型。每個類包含數據說明和一組操作數據或傳遞消息的函數。類的實例稱為對象。
Dart的類與其它語言都有很大的區別,比如在dart的類中可以有無數個構造函數,可以重寫類中的操作符,有默認的構造函數,由於dart沒有接口,所以dart的類也是接口,因此你可以將類作為接口來重新實現。
Dart是一門使用類和單繼承的面向對象語言所有的對象都是類的實例,並且所有的類都是Object
的子類。
二,類定義
- 類的定義用
class
關鍵字 - 如果未顯式定義構造函數,會默認一個空的構造函數
-
類首字母必須大寫
- 使用
new
關鍵字和構造函數
來創建對象 -
class Person { //未定義父類的時候,默認繼承自Object num x; num y; num z; } void main(List<String> args){ var person = new Person();//調用默認的構造函數 person.x = 10; //使用點(
.
)引用實例變量或方法 person.y = 11; person?.z = 12; //如果p不為空,設置它的變量y的值為4 print(person.x); print(person.y); print(person.z); }結果:
10 11 12
三, 實例變量
- 聲明實例變量時,所有未初始化的實例變量的值為null
- 對象的成員包括函數和數據(分別是方法和實例變量)。使用點(
.
)引用實例變量或方法; - 使用
?.
來確認前操作數不為空, 常用來替代.
, 避免左邊操作數為null引發異常 -
void main(){ var point = new Point(); point.x = 4; //使用點(
.
)引用實例變量或方法
point?.y = 5;//如果p不為空,設置它的變量y的值為5
print(point.x); print(point.y);
}class Point {
int x; // null
int y; // null
int z = 0; // 0
}
四,構造函數
- 如果你沒有聲明構造函數,默認有構造函數,默認構造函數沒有參數,調用父類的無參構造函數。子類不能繼承父類的構造函數
class Person { int x; int y; } void main(List<String> args){ var person = new Person(); }
- 構造函數就是一個與類同名的函數,關鍵字 this 是指當前的,只有在命名沖突時有效,否則dart會忽略處理
-
void main(){ var point = new Point(4, 5); }
class Point { int x; int y;
//自己定義的類名構造函數
Point(int x, int y) {
this.x = x;
this.y = y;
}
} -
在Dart中構造函數的名稱可以是類名
ClassName
或者類名和標識符ClassName.identifier
。 其中構造函數名稱是“ClassName”的函數叫“類名構造函數”;構造函數名稱是“ClassName.identifier”的函數叫“命名構造函數”。
(1)類名構造函數 (ClassName)import 'dart:math'; class Point { int y; int x; // 類名構造函數 Point(num x, num y) { this.x = x; this.y = y; } // ..... }
在構造函數里初始化成員屬性是很常見的事情,因此Dart開發了新的語法糖來簡化這種操作,比如將Point的類名構造構造函數改寫成
class Point { num x, y; // 注意x,y的賦值會在構造函數執行之前完成. Point(this.x, this.y); }
(2)命名構造函數(ClassName.identifie)
使用命名構造函數可以為類提供多個構造函數,按官方的說法就是提供額外的清晰度class Point { num x, y; Point(this.x, this.y); // 命名構造函數 Point.origin() { x = 0; y = 0; } }
調用命名構造函數
在命名構造函數里也可以用新的語法糖來簡化這種操作,比如將Point的類名構造構造函數改寫成
-
(2)命名構造函數(ClassName.identifie)
使用命名構造函數可以為類提供多個構造函數,按官方的說法就是提供額外的清晰度
class Point { num x, y; Point(this.x, this.y); // 命名構造函數 Point.origin() { x = 0; y = 0; } }
調用命名構造函數
main(List<String> args) { // 調用命名構造函數 Point point1 = Point.origin(); }
在命名構造函數里也可以用新的語法糖來簡化這種操作,比如將Point的類名構造構造函數改寫成
class Point { num x, y; //類名構造函數 Point(this.x, this.y); // 命名構造函數 Point.origin(this.x,this.y); }
void main(List<String> args){ var point = new Point.Orgin(1,2); print(point.x); print(point.y); }
(3)默認構造函數(前面我我們已經說了,我們放在這里再提一下,方便區分)
如果類中沒有聲明構造函數,Dart會提供一個默認的構造函數。這個默認的構造函數會調用父類的默認構造函數,並且該構造函數是沒有參數的。class Person { int x; int y; } void main(List<String> args){ var person = new Person(); }
-
Dart的第一個版本實例化對象需要new關鍵字,但在Dart 2之后就去掉了new關鍵字
main(List<String> args) { // 調用類名構造函數 Point point1 = Point(3,4); print(point1.x); }
-
調用父類非默認的構造函數(類比下面的重定向理解記憶)
在默認情況下,子類可以調用父類的未命名,無參數的構造函數即默認構造函數。父類的構造函數會在子類的構造函數之前開始調用,如果子類中存在需要初始化的成員屬性,則可以先初始化子類成員屬性,再調用父類的構造函數,執行過程如下
- 初始化子類成員屬性
- 調用父類構造函數
- 子類構造函數
如果父類中沒有默認的構造函數,你必須手動調用父類的構造函數,在子類的構造函數體之前通過
:
指定調用父類構造函數,示例如下// Person類中沒有一個無參數,未命名的構造函數 class Person { String firstName; // 命名構造函數 Person.fromJson(Map data) { print('in Person'); } } class Employee extends Person { // 你必須調用父類的super.fromJson(data). Employee.fromJson(Map data) : super.fromJson(data) { print('in Employee'); } } main() { var emp = new Employee.fromJson({});
}
-
重定向構造函數 (在這里 :有重新指向的含義)
有時構造函數的唯一目的是重定向到同一類中的另一個構造函數。重定向構造函數的主體為空,構造函數調用出現在冒號(
:
)之后 。 大意就是在創建類時,我定義一個命名構造函數,但是這個構造函式的主體我不實現。直接通過:另外一個構造函數。實現對外界傳入的參數接收並賦值給內部的變量。class Point { num x, y; //類名構造函數 Point(this.x, this.y); // 命名構造函數 Point.order(this.x,this.y); Point.origin(num a,num b):this.order(a,b); //重定向構造函數, origin構造函數將外界的傳值,指向給了order構造函數。 } void main(List<String> args){ var point = new Point.origin(1,2); print(point.x); print(point.y); }
- 常量構造函數
如果你的類創建的對象從不改變,你可以創建一些編譯時的常量對象。因此,定義一個const
構造函數,且保證所有的對象變量都是final。
class ImmutablePoint { static final ImmutablePoint origin = const ImmutablePoint(0, 0); final num x, y; const ImmutablePoint(this.x, this.y); }
-
工廠構造函數
在實現一個構造函數時使用
factory
關鍵字,該構造函數並不總是創建其類的新實例。例如,工廠構造函數可能會從緩存中返回實例,也可能會返回子類型的實例。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); } }
注意:工廠構造者對this沒有訪問權限。
像調用任何其他構造函數一樣調用工廠構造函數:
var logger = Logger('UI'); logger.log('Button clicked');
五,方法
方法是為對象提供行為的函數。
- Getters和Setters 方法
(1)Getters和setters是讀取和修改對象的特定方法,每次調用對象的屬性時,Dart都會隱式的調用一次getter方法,這允許你可以在修改或者讀取對象屬時做一些操作。
(2)通過get
和set
關鍵詞重寫對象的默認行為。
class Rectangle { num left, top, width, height; //類名構造函數 Rectangle(this.left, this.top, this.width, this.height); // 重寫right屬性(類比oc記憶,這里多了一個set和get的關鍵字) 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); }
-
實例方法
在對象的實例方法有權限獲取對象變量和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); } }
-
抽象方法
(1) 實例的getter和setter方法就是抽象的方法,定義一個接口,但將其實現留給其他類。
(2)抽象方法使用分號 ; 而不是方法體
(3)抽象方法只存在於抽象類中。abstract class Doer { //...定義實例變量和方法... //定義一個抽象方法 void doSomething(); } class EffectiveDoer extends Doer { void doSomething() { //...實現一個抽象方法... } }
六,抽象類和接口
-
抽象類
使用abstract
修改器可以定義一個抽象類。抽象類是不能被實例化的,但對於定義接口是非常有用的,如果你想實例化抽象類,你必須實現抽象類,才能被實例化
-
// 此對象愛你過是抽象類,因此不能被實例化 abstract class AbstractContainer { // 定義構造函數、字段、方法... void updateChildren(); // 抽象方法 }
-
隱式的接口
每個類都是都是隱式的接口,包括類的方法和屬性。如果你想創建一個類A不繼承B的實現,可以實現B的接口來創建類A。一個類允許通過implements
關鍵詞可以實現多個接口// 每個類都是一個隱式的接口,所以Person類也是個接口,包括成員屬性和方法. class Person { // 可在接口中實現, 但僅對這個庫可見. final _name; // 構造函數不能夠被接口實現 Person(this._name); // 可在接口中實現. String greet(String who) => 'Hello, $who. I am $_name.'; } // 實現Person接口. 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())); }
實現多個接口
class Point implements Comparable, Location {...}
七,類的繼承
- 使用extends創建子類,super引用父類,子類可以重寫實例方法、getter和setter,使用@override注釋重寫,使用@proxy注釋來忽略警告
class Television { void turnOn() { _illuminateDisplay(); _activateIrSensor(); } } class SmartTelevision extends Television { void turnOn(); _bootNetworkInterface(); _initializeMemory(); _upgradeApps(); }
-
重寫成員
可以使用
@override
關鍵字,子類可以重寫實例的方法,getters和settersclass SmartTelevision extends Television { @override void turnOn() {...} // ··· }
-
重寫操作符
你可以重寫以下表中顯示的運算符。例如,如果定義Vecor類,則可以定義
+
方法來添加兩個vectors。
注意:
你可能已經注意到了!=
不是可重寫的運算符。表達式E1!= E2
只是語法上的糖!( E1 = = E2 )
< + | [] > / ^ []= <= ~/ & ~ >= * << == – %
-
可以重寫操作符,下面是重寫加減操作符的示例
如果你需要重寫
==
操作符,請參考操作符教程
-
noSuchMethod()
當用戶調用你定義的類中不存在的屬性與方法時,可以做出一些響應,通過重寫noSuchMethod()
class A { @override void noSuchMethod(Invocation invocation) { print('You tried to use a non-existent member: ' + '${invocation.memberName}'); } }
八,類變量和類方法
使用static
關鍵字實現類范圍的變量和方法。
- 靜態變量
靜態變量(類變量)對於類范圍的狀態和常量非常有用:
靜態變量在使用之前不會初始化。
class Queue { static const initialCapacity = 16; // ··· } void main() { assert(Queue.initialCapacity == 16); }
- 靜態方法
靜態方法(類方法)不能在實例操作,因此它沒有訪問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); }
注意:
(1)為了常用或廣泛使用的實用程序和功能,考慮使用頂層函數,而不是靜態方法。import 'dart:math'; class Point { num x, y; Point(this.x, this.y); } // 頂級函數 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 = distanceBetween(a, b); assert(2.8 < distance && distance < 2.9); print(distance); }
(2)可以使用靜態方法作為編譯時常量。例如,可以將靜態方法作為參數傳遞給常量構造函數。
九,訪問控制
- 默認類中的所有屬性和方法是public的。在dart中,可以在屬性和方法名前添加“_”使私有化。現在讓我們使name屬性私有化。
main(List<String> args) { Dog d = new Dog('Duffy', 5); print(d.name); //This will throw error } class Dog { String _name; //私有的變量 int age; //公有變量
//類名構造函數 Dog(this.name, this.age);
//setter && getter 方法 String get respectedName { return 'Mr.$name'; }
set respectedName(String newName) { name = newName; }
//命名構造函數 Dog.newBorn() { name = 'Doggy'; age = 0; }
//公有實例方法 bark() { print('Bow Wow'); }
//私有的實例方法 _hiddenMethod() { print('I can only be called internally!'); } }
十,枚舉類型
枚舉類型,通常稱為枚舉,是一種特殊類型的類,用於表示固定數量的常量值。
-
使用枚舉
使用enum
關鍵詞來聲明一個枚舉類型enum Color { red, green, blue }
枚舉中的每個值都有一個
index
索引,它返回枚舉聲明中值的從零開始的位置。例如,第一個值具有索引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: // 沒有default,將會報錯 print(aColor); // 'Color.blue' }
枚舉類型有以下限制:
- 你不能子類化、混合或實現枚舉。
- 不能顯式實例化枚舉。
十一,泛型
- 泛型是程序設計語言的一種特性。允許程序員在強類型程序設計語言中編寫代碼時定義一些可變部分,那些部分在使用前必須作出指明。
- 從字面的意思理解來看,泛型,泛就是模糊、暫不確定暫定的意思。可以這樣理解,使用泛型就是,定義的一個類型,類型暫不確定,給使用給一個占位符給代替,在使用的時候可以給確定其定義的類型。
- 泛型,基本上強類型語言都支持泛型,比如java,Oc,swift 所以Dart也不例外
- dart全面支持泛型。假設你想在你定義的類中,想持有任意類型的數據。如下是怎樣使用泛型定義這樣的類。
main(List<String> args) { DataHolder<String> dataHolder = new DataHolder('Some data'); print(dataHolder.getData()); dataHolder.setData('New Data'); print(dataHolder.getData()); } class DataHolder<T> { T data; DataHolder(this.data); getData() { return data; } setData(data) { this.data = data; } }