九: Flutter之Dart第六節(jié)(類和對象)?
Dart 是一個面向?qū)ο蟮恼Z言、面向?qū)ο笾蟹浅V匾母拍罹褪穷愂逅臁㈩惍a(chǎn)生了對象他炊。
這一節(jié),我們就具體來學(xué)習(xí)類和對象已艰、但是Dart對類進(jìn)行了很多其他語言沒有的特性痊末、一起探討。9.1: 類的定義
在Dart中哩掺、定義類用class關(guān)鍵字凿叠。
類通常有兩部分組成:成員(member) 和 方法 (method)。
定義類的偽代碼如下:class 類名 { 類型 成員名 返回值類型 方法名(參數(shù)列表) { 方法體 } }
編寫一個簡單的Person類
- 這里有一個注意點(diǎn):我們在方法中使用屬性(成員變量/實(shí)例變量)時嚼吞、并沒有加this盒件;
- Dart的開發(fā)風(fēng)格中、在方法中通常使用屬性時舱禽、會省略this炒刁、但是有命名沖突時、this不能省略誊稚。
class Person { String name; eat() { print('$name在吃東西'); } } main(List<String> args) { // 2: 我們在方法中使用屬性并沒有使用this翔始、一般我們都是省略的罗心、但是如果在方法中使用屬性的時候、如果有命名 // 沖突的話城瞎、this不能省略 // 1: 創(chuàng)建類的對象, 直接使用Person() 也可以創(chuàng)建 var p = new Person(); // 2: 給對象的屬性賦值 p.name = 'lishengbing'; // 3: 調(diào)用對象方法 p.eat(); //打硬趁啤: lishengbing在吃東西 }
9.2: 構(gòu)造方法
9.2.1: 普通構(gòu)造方法
我們知道、當(dāng)通過類創(chuàng)建一個對象時全谤、會調(diào)用這個類的構(gòu)造方法
當(dāng)類中沒有明確指定的構(gòu)造方法時肤晓、將默認(rèn)擁有一個無參的構(gòu)造方法
前面的Person中我們就是在調(diào)用這個構(gòu)造方法、我們也可以根據(jù)自己的需求认然、定義自己的構(gòu)造方法补憾。
注意1: 當(dāng)有了自己的構(gòu)造方法時、默認(rèn)的構(gòu)造方法將會失效卷员、不能使用
-- 當(dāng)然盈匾、你可能希望明確的寫一個默認(rèn)的構(gòu)造方法、但是會和我們自定義的構(gòu)造方法沖突
-- 這是因為Dart語言本身不支持函數(shù)的重載(名稱相同毕骡、參數(shù)不同的方式)注意2: 這里我還實(shí)現(xiàn)了toString方法削饵。
class Person { String name; int age; // 明確指定了構(gòu)造方法、如果沒有指定默認(rèn)是Person() Person(String name, int age) { this.name = name; this.age = age; } @override String toString() { return 'name = ${name}, age = ${age}'; } }
另外未巫、在實(shí)現(xiàn)構(gòu)造方法時窿撬、通常做的事情就是通過 **參數(shù)給屬性 **賦值,如上this.name = name
為了簡化了這一過程叙凡、Dart提供了一種更加簡潔的語法糖形式
上面的構(gòu)造方法可以優(yōu)化成下面的寫法:// 明確指定了構(gòu)造方法劈伴、如果沒有指定默認(rèn)是Person() Person(String name, int age) { this.name = name; this.age = age; } // 等同于上面優(yōu)化構(gòu)造方法 Person(this.name, this.age);
9.2.2: 命名構(gòu)造方法
但是在開發(fā)中、我們確實(shí)希望實(shí)現(xiàn)更多的構(gòu)造方法握爷、怎么辦呢跛璧?
- 因為不支持方法(函數(shù))的重載、所以我們沒辦法創(chuàng)建相同名稱的構(gòu)造方法新啼。我們需要使用命名構(gòu)造方法追城。
class People { String name; int age; People() { name = ''; age = 0; } // 命名構(gòu)造方法, withArgments可以隨便變化 People.withArgments(String name, int age) { this.name = name; this.age = age; } // 優(yōu)化上面的構(gòu)造方法 //People.withArgments(this.name, this.age); @override String toString() { return 'name=${name} age=${age}'; } } /* * name= age=0 name=lishengbing age=28 */ main(List<String> args) { var p = new People(); print(p); var p1 = new People.withArgments('lishengbing', 28); print(p1); }
在之后的開發(fā)中、我們也可以利用命名構(gòu)造方法燥撞、提供更加便捷的創(chuàng)建對象的方式
- 比如開發(fā)中座柱、我們需要經(jīng)常將一個Map轉(zhuǎn)成對象、可以提供如下的構(gòu)造方法
// 新的構(gòu)造方法 People.fromMap(Map<String, Object> map) { this.name = map['name']; this.age = map['age']; } var p3 = new People.fromMap({'name': 'lishengbing', 'age': 29}); // name=lishengbing age=29 print(p3);
9.2.3: 初始化列表
我們來重新定義一個類Point物舒、傳入x/y辆布、可以得到它的距離distance:
初始化列表 class Point { final num x; final num y; final distance; // 錯誤寫法 Point(this.x, this.y) { distance = sqrt(x * x + y * y); }*/ // 正確寫法 Point(this.x, this.y) : distance = sqrt(x * x + y * y); }
上面這種初始化變量的方法、我們稱之為初始化列表(Initializer list)
9.2.4: 重定向構(gòu)造方法
在某些情況下茶鉴、我們希望在一個構(gòu)造方法中去調(diào)用另一個構(gòu)造方法、這時候我們就可以使用重定向構(gòu)造方法景用。
- 在一個構(gòu)造函數(shù)中涵叮、去調(diào)用另一個構(gòu)造函數(shù)(注意:是在冒號后面使用this調(diào)用)
class Person01 { String name; int age; Person01(this.name, this.age); Person01.fromName(String name) : this('lishengbing', 30); }
9.2.5: 常量構(gòu)造方法
在某些時候下惭蹂、傳入相同值時、我們希望返回同一個對象割粮、這個時候盾碗、可以使用常量構(gòu)造方法
默認(rèn)情況下、創(chuàng)建對象時舀瓢、即使傳入相同的參數(shù)廷雅、創(chuàng)建出來的對象也不是同一個對象,看下面的代碼:
- 我們使用identical(對象1京髓, 對象2)函數(shù)來判斷兩個對象是否是同一個對象
class Person02 { String name; Person02(this.name); } main(List<String> args) { var p1 = Person02('lishengbing'); var p2 = Person02('lishengbing'); // false print(identical(p1, p2)); }
- 但是如果將構(gòu)造方法前加上 const進(jìn)行修飾的話航缀、那么可以保證同一個參數(shù)、創(chuàng)建出來的對象是相同的堰怨。
這樣的構(gòu)造函數(shù)就叫做常量構(gòu)造方法class Person02 { final String name; const Person02(this.name); } main(List<String> args) { var p1 = const Person02('lishengbing'); var p2 = const Person02('lishengbing'); // true print(identical(p1, p2)); }
常量構(gòu)造方法有一些注意點(diǎn):
- 注意一:擁有常量構(gòu)造方法的類中芥玉、所有的成員變量必須是final學(xué)校修飾的、如final String name;
- 注意二:為了可以使用常量構(gòu)造方法备图、創(chuàng)建出相同的對象灿巧,不再使用new 關(guān)鍵字、而是使用const關(guān)鍵字
-- 如果是將結(jié)果賦值給const修飾的標(biāo)識符時揽涮、const可以省略抠藕。9.2.5: 工廠構(gòu)造方法
Dart 提供了factory關(guān)鍵字、用于通過工廠去獲取對象蒋困。
工廠獲取得到的對象傳入同一個參數(shù)盾似、得到的是同一個對象
傳入不同的參數(shù)、獲取的對象不是同一個對象家破。main(List<String> args) { var p1 = Person03('object'); var p2 = Person03('object'); // 工廠構(gòu)造方法獲取對象比較=true print('工廠構(gòu)造方法獲取對象比較=${identical(p1, p2)}'); } class Person03 { String name; static final Map<String, Person03> _cache = <String, Person03>{}; factory Person03(String name) { if (_cache.containsKey(name)) { return _cache[name]; }else { final p = Person03._internal(name); _cache[name] = p; return p; } } Person03._internal(this.name); }
9.2.6: setting & getting
默認(rèn)情況下颜说、Dart中類定義的屬性是可以被外界直接訪問的
但是某些情況下、我們希望監(jiān)控這個類的屬性被訪問的過程汰聋、這個時候就可以使用setting 和 getting了main(List<String> args) { final d = Dog('黃色'); //打印就是: dog color = 黑色 d.setColor = '黑色'; //打印就是: dog color = 紅色 d.color = '紅色'; print('dog color = ${d.getColor}'); } class Dog { String color; String get getColor { return color; } set setColor(String color) { this.color = color; } Dog(this.color); }
9.2.7: 類的繼承(僅支持單繼承门粪、多繼承不可以)
1: 面向?qū)ο蟮钠渲幸淮筇匦跃褪抢^承、繼承不僅可以減少我們的代碼量烹困、也是多態(tài)的使用前提
2: dart 中的繼承使用extends 關(guān)鍵字玄妈、子類中使用super來訪問父類;
3: 父類中的所有成員變量和方法都會被繼承髓梅、但是構(gòu)造方法除外.
main(List<String> args) { var p = new Person04(); p.age = 10; p.run(); /// 奔跑ing /// 繼承=10 print('繼承=${p.age}'); } class Animal { int age; run() { print('奔跑ing'); } } class Person04 extends Animal { }
9.2.7(1): 子類可以擁有自己的成員變量拟蜻、并且可以對父類的方法進(jìn)行重寫
class Person04 extends Animal { @override run() { // TODO: implement run 1: 如果子類重寫了父類的方法,打印父類的方法 //我是Person04類的重寫run方法 //繼承=10 print('我是Person04類的重寫run方法'); } }
9.2.7(2): 子類中可以調(diào)用父類的構(gòu)造方法枯饿、對某些屬性進(jìn)行初始化
- 子類的構(gòu)造方法在執(zhí)行前酝锅, 將隱式調(diào)用父類的默認(rèn)的無參數(shù)的構(gòu)造方法(沒有參數(shù)且類同名的構(gòu)造方法)
- 如果父類沒有無參默認(rèn)構(gòu)造方法、則子類的構(gòu)造方法必須在初始化列表中通過super顯式調(diào)用父類的某個構(gòu)造方法奢方。
class Animal { int age; run() { print('奔跑ing'); } // 只要這樣寫搔扁、該類就沒有了無參默認(rèn)構(gòu)造函數(shù) Animal(this.age); } class Person04 extends Animal { String name; Person04(String name, int age) : name=name, super(age); @override run() { // TODO: implement run // 1: 如果子類重寫了父類的方法爸舒,打印父類的方法 // 我是Person04類的重寫run方法 // 繼承=10 print('我是Person04類的重寫run方法'); } @override String toString() { print('toString-'); } }
9.2.8: 抽象類
我們知道、繼承是多態(tài)使用的前提
所以在定義很多通用的通用接口時我們通常會讓調(diào)用者傳入父類稿蹲、通過多態(tài)類實(shí)現(xiàn)更加靈活的調(diào)用方式扭勉。但是、父類本身可能并不需要對某些方法進(jìn)行具體的實(shí)現(xiàn)苛聘、所以父類中定義的方法涂炎、我們可以定義為抽象類。
什么是抽象方法设哗?在Dart中沒有具體實(shí)現(xiàn)的方法(沒有方法體)唱捣、就是抽象方法。
- 抽象方法熬拒、必須存在于抽象類中爷光。
- 抽象類是使用abstract 聲明的類。
下面這個Shape類就是一個抽象類澎粟、其中包含了一個抽象方法
abstract class Shape {
getArea();
}
class Circle extends Shape {
double r;
Circle(this.r);
@override
getArea() {
// TODO: implement getArea
return r * r * 3.14;
}
}
class Reactangle extends Shape {
double w;
double h;
Reactangle(this.w, this.h);
@override
getArea() {
// TODO: implement getArea
return w * h;
}
}
注意事項:
- 注意一:** 抽象類中不能實(shí)例化蛀序;
- 注意二:** 抽象類中的抽象方法必須被子類實(shí)現(xiàn)、抽象類中的已經(jīng)被實(shí)現(xiàn)方法活烙、可以不被子類重寫徐裸;
9.2.9: 隱式接口
Dart中的接口比較特殊、沒有一個專門的關(guān)鍵字來聲明接口
默認(rèn)情況下啸盏、定義的每一個類都相當(dāng)于默認(rèn)也聲明了一個接口重贺、可以由其他的類來實(shí)現(xiàn)(因為Dart不支持多繼承)在開發(fā)中、我們通常將用于給別人來實(shí)現(xiàn)的類聲明為抽象類:
abstract class Runner {
run();
}
abstract class Flyer {
fly();
}
class SuperMan implements Runner, Flyer {
@override
run() {
// TODO: implement run
print('超人在跑');
}
@override
fly() {
// TODO: implement fly
print('超人在飛');
}
}
9.2.10: Mixin混入
在通過implements實(shí)現(xiàn)某個類時回懦、類中所有的方法都必須被重載實(shí)現(xiàn)(無論這個類原來是否已經(jīng)實(shí)現(xiàn)過該方法)
但是在某些情況下气笙、一個類可能希望直接復(fù)用之前類的原有實(shí)現(xiàn)方案、怎么做呢怯晕?
- 使用繼承嗎潜圃?但是Dart只支持單繼承、那么意味著你只能復(fù)用一個類的實(shí)現(xiàn)
Dart提供了另外一種方案:Mixin混入的方式
- 除了可以class 定義類之外舟茶、也可以通過mixin關(guān)鍵字來定義一個類
- 只是通過mixin定義的類用于被其他類混入使用谭期、通過with關(guān)鍵字來進(jìn)行混入。
main(List<String> args) {
var superMan = SuperMain();
superMan.run();
superMan.fly();
/*
在奔跑...
在飛翔...
*/
}
mixin Runner1 {
run() {
print('在奔跑...');
}
}
mixin Flyer1 {
fly() {
print('在飛翔...');
}
}
/// implements的方式必須要求對其中的方法進(jìn)行重新實(shí)現(xiàn)
class SuperMain0 implements Runner1, Flyer1 {
@override
run() {
// TODO: implement run
return null;
}
@override
fly() {
// TODO: implement fly
return null;
}
}
class SuperMain with Runner1, Flyer1 {
}
9.2.11: 類成員和方法
前面我們在類中定義的成員和方法都屬于對象級別的吧凉、在開發(fā)中隧出、我們有時候也需要定義類級別的成員和方法
在Dart中我們使用static關(guān)鍵字類定義:
main(List<String> args) {
var stu = Student();
stu.name = 'lishengbing';
stu.age = 66;
stu.study();
Student.time = '早上10點(diǎn)';
Student.attendClass();
// lishengbing在學(xué)習(xí)
// 去上課
}
class Student {
String name;
int age;
static String time;
study() {
print('$name在學(xué)習(xí)');
}
static attendClass() {
print('去上課');
}
}
9.2.12: 枚舉的類型
枚舉在開發(fā)中也非常常見、枚舉也是一種特殊的類阀捅、通常用于表示數(shù)量的常量值胀瞪。
1: 枚舉的定義
枚舉使用enum關(guān)鍵字來進(jìn)行定義
// 1: 枚舉的定義
main(List<String> args) {
// Colors.red
print(Colors.red);
// values, [Colors.red, Colors.green, Colors.blue]
print(Colors.values);
// index, 0
print(Colors.red.index);
}
enum Colors {
red,
green,
blue
}
2: 枚舉的屬性
枚舉類型中有兩個比較常見的屬性
- index: 用于表示每個枚舉常量的索引、從0開始
如:print(Colors.red.index);- values:包含每個枚舉值的List
如:print(Colors.values);3: 枚舉的注意事項
- 注意1: 您不能子類化饲鄙、混合或者實(shí)現(xiàn)枚舉赏廓。
- 注意2: 不能顯式實(shí)例化一個枚舉涵紊。
9.3: 泛型
1: 為什么使用范型?
......
2: List和Map的范型
main(List<String> args) {
// 1: 創(chuàng)建List的方式
var name1 = ['lishengbing', 'zhangsan', 'wangxiao', 111];
// List<String>
// List<Object>
print(name1.runtimeType);
// 限制類型
var name2 = <String>['1', '2', '3'];
// List<String>
print(name2.runtimeType);
// 2: Map使用泛型的寫法
var info1 = {'name': 'li', 1: 'one', 'age': 20};
// _InternalLinkedHashMap<Object, Object>
print(info1.runtimeType);
// 2: 對類型進(jìn)行限制
Map<String, String> info2 = {'name': 'li', 'age': '20'};
// 限制寫法1
// info2=_InternalLinkedHashMap<String, String>
print('info2=${info2.runtimeType}');
var info3 = <String, String>{'name': 'li', 'age': '20'};
// 限制寫法2
// info3=_InternalLinkedHashMap<String, String>
print('info3=${info3.runtimeType}');
}
3: 類定義的泛型
如果我們需要定義一個類、用于存儲位置信息Location幔摸、但是并不確定使用者使用的是int類型、還是double類型颤练、甚至是一個字符串類型既忆、這個時候該怎么定義呢?
- 一種方案是使用Object類型嗦玖、但是在之后使用時候非常不方便
- 另一種方案就是使用泛型
Location類的定義:Object類 || 泛型方式
main13(List<String> args) {
Location l = Location(10, 20);
/// int
print(l.x.runtimeType);
}
class Location {
Object x;
Object y;
Location(this.x, this.y);
}
main(List<String> args) {
Location2 l1 = Location2<int>(10, 30);
// Location2<int>
print('int-l1=${l1.runtimeType}');
Location2 l2 = Location2<String>('30', '40');
// Location2<String>
print('String-l2=${l2.runtimeType}');
}
// 泛型寫法
class Location2<T> {
T x;
T y;
Location2(this.x, this.y);
}
- 如果我們希望類型只能是num類型患雇、怎么做呢?
// 如果希望類型只能是num類型
main(List<String> args) {
Location3 l3 = Location3(10, 20);
// Location3<num>
print(l3.runtimeType);
}
class Location3<T extends num> {
T x;
T y;
Location3(this.x, this.y);
}
4: 泛型方法的定義
最初宇挫、Dart僅僅在類中支持泛型苛吱、后來一種稱之為泛型方法的新語法允許在方法和函數(shù)中使用類型參數(shù)。
main(List<String> args) {
var names = ['1', '2'];
var first = getFirst(names);
/// first = 1, type=String
print('first = ${first}, type=${first.runtimeType}');
}
T getFirst<T>(List<T> ts) {
return ts[0];
}