類
基本概念
- Object類:Dart是一個物件導向程式語言,每個物件都是一個類的例項,所有的類都繼承於Object。
//定義一個類
class Person {}
複製程式碼
- 類的繼承性:每個類(Object 除外) 都只有一個超類(父類),一個類的程式碼可以被其他多個類繼承來使用。
- 建立新物件:使用new 關鍵字和建構函式來,建構函式名字可以類名;
- 物件的成員:包括方法和資料 (函式和示例變數),使用點(.)來引用物件的變數或者方法,使用 ?. 來替代 . 來訪問物件可以避免當左邊物件為 null 時候丟擲異常.。
// If p is non-null, set its y value to 4.
p?.y = 4;
複製程式碼
- 獲取例項物件的型別:可以使用 Object 的 runtimeType 屬性來判斷例項的型別,該屬性返回一個 Type物件。
class Student {}
main() {
var a = new Student();
print(a.runtimeType); //Student
}
複製程式碼
類變數和函式
靜態變數
- 靜態變數:static關鍵字修飾的類級別的變數,直接使用"類名.x"來呼叫;
- 靜態變數使用lowerCamelCase 來命名常量。
- 靜態變數在定義的時候可以不初始化,此時預設為null。
class Person {
static String name;
static const sex = "男";
}
main() {
print(Person.name);
print(Person.sex);
}
複製程式碼
靜態函式
static 修飾的函式即靜態函式,靜態函式不再類例項上執行, 所以無法訪問 this。
注意: 對於通用的或者經常使用的靜態函式,考慮 使用頂級方法而不是靜態函式。
class Person {
static void printInfo(){
print("hello world");
}
}
main() {
Person.printInfo();
}
複製程式碼
例項的私有屬性
和Java不同的是,Dart沒有public、protected、和private關鍵字。如果一個識別符號以 (_) 開頭,則該識別符號在庫內是私有的。
例項變數
- 類的例項變數,所有沒有初始化的變數值都是null。
- 如果你在例項變數定義的時候初始化該變數(不是 在建構函式或者其他方法中初始化),改值是在例項建立的時候 初始化的,也就是在建構函式和初始化引數列 表執行之前。
- 每個例項變數都會自動生成一個getter和setter方法(隱含的)。
class Person {
//類的例項變數
String name;
String sex;
int age;
}
main() {
var p = new Person();
p.age = 24; // Use the setter method for x.
print(p.age.toString());
}
複製程式碼
this關鍵字
this 關鍵字指當前的例項,只有當名字衝突的時候才使用 this,Dart程式碼風格樣式推薦忽略 this。
class Point {
num x;
num y;
Point(num x, num y) {
// There's a better way to do this, stay tuned.
this.x = x;
this.y = y;
}
}
複製程式碼
建構函式賦值例項變數簡化寫法:
class Point {
num x;
num y;
//由於把建構函式引數賦值給例項變數的場景太常見了, Dart 提供了一個語法糖來簡化這個操作:
// Syntactic sugar for setting x and y
// before the constructor body runs.
Point(this.x, this.y);
}
複製程式碼
函式
函式是類中定義的方法,是類物件的行為。
例項函式
例項函式是類中定義的普通方法,類例項物件可以呼叫的方法,物件的例項函式可以訪問this;
class Person {
String name;
printName() {
print(this.name);
}
}
main() {
new Person()
..name = 'ethan'
..printName();
}
複製程式碼
getter and setter
getter 和 setter 是用來設定和訪問物件屬性的特殊函式,定義時需要在變數名前加get或set標識。
特點:
- 每個例項變數預設隱含一個getter,如果變數不是final型別,則還會隱含一個 setter。
- 可以通過getter 和 setter 來建立新的屬性,使用 get 和 set 關鍵字定義 getter和setter;
- 如果定義一個帶get標識的變數,則例項物件可以直接通過“物件.x”來獲取變數,但是這個變數沒有setter,此時必須也實現setter,才可以通過“物件.x=n”來改變此變數;
class Person {
String name;
String prefix;
Person(this.name);
String get description => prefix + " " + name;
set description(String prefix) => this.prefix = prefix;
}
main() {
var p = new Person("ethan");
p.description = 'x';
print(p.description);
}
複製程式碼
抽象函式
抽象函式是沒有函式體的函式。
例項函式、 getter、和 setter 函式可以為抽象函式, 抽象函式是隻定義函式介面但是沒有實現的函式,由子類來 實現該函式。
示例:
//抽象類
abstract class Doer {
//抽象方法
void doSomething();
}
//子類繼承抽象類,必須實現抽象方法
class EffectiveDoer extends Doer {
void doSomething() {
print("hello");
}
}
複製程式碼
建構函式
概述
定義一個和類名字一樣的方法就定義了一個建構函式。 建構函式用來生成一個物件的新例項;
- 預設建構函式:與類同名,配合new關鍵字建立例項。如果沒有定義建構函式,則會有個預設建構函式。 預設建構函式沒有引數,並且會呼叫超類的 沒有引數的建構函式。
- 建構函式不會繼承:子類不會繼承超類的建構函式。 子類如果沒有定義建構函式,則只有一個預設建構函式 (沒有名字沒有引數)。
- 命名建構函式:定義格式為“類名.函式名(...)”,建立物件例項時也使用“類名.函式名(...)”.使用命名建構函式可以為一個類實現多個建構函式, 或者使用命名建構函式來更清晰的表明你的意圖;
class Person {
//類的例項變數
String name;
int age;
//預設建構函式
Person();
// Person(this.name, this.age);
//命名建構函式
Person.fromJson(Map jsonMap) {
name = jsonMap['name'];
age = jsonMap['age'];
}
}
main() {
var p = Person.fromJson({'name': "ethan", "age": 11});
print(p.name);
print(p.age);
}
複製程式碼
呼叫超類建構函式
預設呼叫方式
子類的建構函式會自動呼叫超類的 無名無引數的預設建構函式。 超類的建構函式在子類建構函式體開始執行的位置呼叫。 如果提供了一個 initializer list(初始化引數列表) ,則初始化引數列表在超類建構函式執行之前執行。
下面是建構函式執行順序:
1. initializer list(初始化引數列表)
2. superclass’s no-arg constructor(超類的無名建構函式)
3. main class’s no-arg constructor(主類的無名建構函式)
複製程式碼
超類是無引數建構函式
如果超類沒有無名無引數建構函式, 則你需要手工的呼叫超類的其他建構函式。 在建構函式引數後使用冒號 (:) + super.xxx() 可以呼叫 超類建構函式。
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({});
//in Person
//in Employee
}
複製程式碼
注意:
- 如果在建構函式的初始化列表中使用 super(),需要把它放到最後。
- 呼叫超類建構函式的引數無法訪問 this。 例如,引數可以為靜態函式但是不能是例項函式。
建構函式初始化列表
在建構函式體執行之前除了可以呼叫超類建構函式之外,還可以 初始化例項引數。 使用逗號分隔初始化表示式。
警告: 初始化表示式等號右邊的部分不能訪問 this。
class Point {
num x;
num y;
Point(this.x, this.y);
// Initializer list sets instance variables before
// the constructor body runs.
Point.fromJson(Map jsonMap)
: x = jsonMap['x'],
y = jsonMap['y'] {
print('In Point.fromJson(): ($x, $y)');
}
}
複製程式碼
重定向建構函式
有時候一個建構函式會調動類中的其他建構函式。 一個重定向建構函式是沒有程式碼的,在建構函式宣告後,使用 冒號呼叫其他建構函式。
class Point {
num x;
num y;
// The main constructor for this class.
Point(this.x, this.y);
// Delegates to the main constructor.
Point.alongXAxis(num x) : this(x, 0);
}
複製程式碼
常量建構函式
如果你的類提供一個狀態不變的物件,你可以把這些物件 定義為編譯時常量。要實現這個功能,需要定義一個 const 建構函式, 並且宣告所有類的變數為 final。
- 使用常量建構函式 可以建立編譯時常量,要使用常量建構函式只需要用const替代new 即可;
- 兩個使用常量建構函式建立,並且建立時的內容一樣常量物件,其實是同一個物件;
class ImmutablePoint {
final num x;
final num y;
const ImmutablePoint(this.x, this.y);
static final ImmutablePoint origin =
const ImmutablePoint(0, 0);
}
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);
assert(identical(a, b)); // They are the same instance!
複製程式碼
工廠方法建構函式
如果一個建構函式並不總是返回一個新的物件,則使用 factory 來定義 這個建構函式。例如,一個工廠建構函式 可能從快取中獲取一個例項並返回,或者 返回一個子型別的例項。
工廠建構函式無法訪問 this。
示例:定義工廠建構函式。
class Logger {
final String name;
bool flag = false;
//_cache: 靜態私有變數快取例項物件
static final Map<String, Logger> _cache = <String, Logger>{};
factory Logger(String name) {
if (_cache.containsKey(name)) {
//獲取快取
return _cache[name];
} else {
//快取
final logger = new Logger._internal(name);
_cache[name] = logger;
return logger;
}
}
//命名建構函式
Logger._internal(this.name);
//改變flag的值
void changeFlag() {
flag = !flag;
}
}
main() {
var logger = new Logger('LOG');
print("flag = " + logger.flag.toString());
logger.changeFlag();
//快取的logger物件
print("flag = " + new Logger('LOG').flag.toString());
//新的logger物件
print("flag = " + new Logger('X').flag.toString());
}
複製程式碼
//執行結果:
flag = false
flag = true
flag = false
複製程式碼
可覆寫的操作符
下表中的操作符可以被覆寫:
< + | []
> / ^ []=
<= ~/ & ~
>= * << ==
– % >>
複製程式碼
覆寫示例
物件加減
覆寫+-法:
class Vector {
final int x;
final int y;
const Vector(this.x, this.y);
/// Overrides + (a + b).
Vector operator +(Vector v) {
return new Vector(x + v.x, y + v.y);
}
/// Overrides - (a - b).
Vector operator -(Vector v) {
return new Vector(x - v.x, y - v.y);
}
}
main() {
final v = new Vector(2, 3);
final w = new Vector(2, 2);
// v == (2, 3)
assert(v.x == 2 && v.y == 3);
// v + w == (4, 5)
assert((v + w).x == 4 && (v + w).y == 5);
// v - w == (0, 1)
assert((v - w).x == 0 && (v - w).y == 1);
}
複製程式碼
覆寫==
Dart 中的每個物件都有一個整數 hash 碼,這樣每個物件都 可以當做 map 的 key 來用。但是,你可以覆寫 hashCode getter 來自定義 hash 碼的實現,如果你這樣做了,你也需要 同時覆寫 == 操作符。相等的物件(使用 == 比較)的 hash 碼應該一樣。hash code 碼並不要求是唯一的, 但是應該具有良好的分佈形態。
如果你覆寫了 == ,則還應該覆寫物件的 hashCode getter 函式。
class Person {
final String firstName, lastName;
Person(this.firstName, this.lastName);
// Override hashCode using strategy from Effective Java,
// Chapter 11.
int get hashCode {
int result = 17;
result = 37 * result + firstName.hashCode;
result = 37 * result + lastName.hashCode;
return result;
}
// You should generally implement operator == if you
// override hashCode.
bool operator ==(other) {
if (other is! Person) return false;
Person person = other;
return (person.firstName == firstName &&
person.lastName == lastName);
}
}
main() {
var p1 = new Person('bob', 'smith');
var p2 = new Person('bob', 'smith');
var p3 = 'not a person';
assert(p1.hashCode == p2.hashCode);
assert(p1 == p2);
assert(p1 != p3);
}
複製程式碼
抽象類
使用“abstract”關鍵字修飾的類為抽象類,不能例項化,抽象類的方法可以實現也可以不實現;
- 如果你希望你的抽象類 是可示例化的,則定義一個 工廠建構函式。
- 抽象方法即沒有方法體的方法,普通類不能有抽象方法;
//抽象類
abstract class Person {
void eat();
}
//子類實現抽象類的方法
class Student extends Person {
void eat(){
print("i can eat");
}
}
main() {
new Student().eat();
}
複製程式碼
類的隱式介面
- 每個類都隱式的定義了一個包含所有例項成員的介面, 並且這個類實現了這個介面。
- 一個類可以通過 implements 關鍵字來實現一個或者多個介面, 並實現每個介面定義的 API。
如果你想 建立類 A 來支援 類 B 的 api,而不想繼承 B 的實現, 則類 A 應該實現 B 的介面。例如:
class Person {
final _name;
Person(this._name);
String greet(who) => 'hello, $who. I am $_name.';
}
class Animal {
void eat() {}
}
//實現多個介面
class StudentImpl implements Person, Animal {
final _name = "";
String greet(who) => 'Hi $who. Do you know who I am?';
void eat() {
print("i can eat");
}
}
greetBob(Person person) => person.greet('bob');
main() {
print(greetBob(new Person('kathy')));
print(greetBob(new StudentImpl()));
}
複製程式碼
類繼承
- 子類通過extends來繼承父類;
- 通過super來呼叫父類方法;
- 可以使用 @override註解一個函式,來表明你的函式是想覆寫超類的一個函式;
繼承示例如下:
class Animal {
void eat() {
print("i can eat");
}
void greet() {
print("hello everyone !");
}
}
class Person extends Animal {
void printFeature() {
super.eat();
}
//重寫父類函式
@override
void greet() {
print("hello i am person");
}
}
main() {
new Person().greet();
}
複製程式碼
列舉型別
列舉型別通常稱之為 enumerations 或者 enums, 是一種特殊的類,用來表現一個固定 數目的常量。
- 使用 enum 關鍵字來定義列舉型別;
- 列舉型別中的每個值都有一個 index getter 函式, 該函式返回該值在列舉型別定義中的位置(從 0 開始)。
- 列舉的 values 常量可以返回 所有的列舉值。
- 可以在 switch 語句 中使用列舉。 如果在 switch (e) 中的 e 的型別為列舉類, 如果你沒有處理所有該列舉型別的值的話,則會丟擲一個警告;
- 列舉類無法繼承列舉型別、無法使用 mix in、無法實現一個列舉型別;
- 列舉類無法顯示的初始化一個列舉型別;
enum Color { red, green, blue }
judgeType(Color color) {
switch (color) {
case Color.blue:
print("yes i am blue");
break;
default:
print("no");
break;
}
}
main() {
print(Color.green.index); // 1
print(Color.values); //列舉值列表[Color.red, Color.green, Color.blue]
//列舉應用switch
judgeType(Color.red);
judgeType(Color.blue);
}
複製程式碼
mixins重用類
mixins可以實現類似繼承多個類功能的效果,重用多個類的屬性。
- mixins使用with關鍵字後跟需要的類;
- Dart 1.13之前,定義一個類繼承Object,該類沒有建構函式,不能呼叫 super ,則該類就是一個 mixin。
- Dart 1.13+,mixins可以繼承其他類,不再限制為繼承Object,可以呼叫 super()。
super mixins” 還 無法在 dart2js 中使用 並且需要在 dartanalyzer 中使用 --supermixin 引數。
class Animal {
final name = "ethan";
}
class Musical {
void printFeature() {
print("i can song");
}
}
class Person {
String iq;
}
class Student extends Person with Animal, Musical {
void printInfo() {
print("name=" + name);
super.printFeature();
printFeature();
super.iq = "100";
print("IQ=" + iq);
}
}
main() {
new Student().printInfo();
}
複製程式碼