文章大綱
引言
前一篇文章Flutter入門——Flutter開發環境搭建和Dart基礎語法總結(一)介紹了Flutter的環境搭建和Dart中內置數據類型、類型轉換、變量定義、控制語句、操作符的基本語法,這一篇接着小結方法、類、繼承、泛型、庫的相關知識。
一、Function方法
Dart是一種的面向對象的語言,所以即使是方法也是對象,並且有一個類型Function,這意味着方法可以賦值給變量或作爲參數傳遞給其他方法。
1、聲明格式
在Dart中方法聲明的基本格式: 返回值類型 方法名(方法簽名){},其中返回值的類型不是必須要聲明的是可省(如果沒有顯式聲明返回值類型時會默認當做dynamic處理,而且返回值沒有類型推斷),如果在方法體裏沒有顯示聲明return 語句,那麼Dart 會自動在最後添加上return null,而且還可以在方法體內嵌套另一個方法,另外,參數分爲有兩種類型的參數:必選參數(調用時必須傳遞)和可選參數。所有的必選參數都應放在可選參數之前,當必選參數已經全部列出時,才能在後面加入可選參數。
void main() {
print(test2());//先輸出787,再輸出null
}
test(){
print("787");
}
當方法體當且僅當含有一個表達式時可以使用一種簡寫的方法:=> 表達式; (即{ 一句表達式 }的簡寫)。
test() =>print("787");
2、帶可選參數類型的方法
可選參數按類型可分爲:可選位置參數或者可選命名參數,但不能既是可選位置參數又是可選命名參數,兩者都可以定義默認值,但是默認值必須是編譯時的常量,如果沒有爲之提供默認值,那麼該參數的默認值將會是 null,最後參數的定義與變量定義語法大同小異,具體根據業務來定。
2.1、可選命名參數
把方法的一組參數放在{}之內就可以把它們標記爲可選命名參數,在調用的時候傳遞時必須用形參名:實參值 傳遞
void main() {
talk("hello flutter");//輸出 hello flutter
talk("hello flutter",talker:"crazy");//輸出hello flutter by crazy
// talk("hello flutter","crazy"); 編譯出錯
talk2("hello flutter");//輸出hello flutter by Mo
talk2("hello flutter",talker:"CRAZY");//輸出hello flutter by CRAZY
}
talk(String content,{String talker}){
if(talker==null){
print(content);
}else{
print(content +" by "+talker);
}
}
talk2(String content,{String talker='Mo'}){
if(talker==null){
print(content);
}else{
print(content +" by "+talker);
}
}
2.2、可選位置參數
把方法的一組參數放在[]之內就可以把它們標記爲可選位置參數。
void main() {
speech("hello dart");//輸出hello dart by crazymo
speech("hello dart","cmo");//輸出hello dart by cmo
speech2("hello dart");//輸出hello dart
}
speech(String content,[String speaker='crazymo']){
if(speaker==null){
print(content);
}else{
print(content +" by "+speaker);
}
}
//默認值有否不能作爲方法重載的一句
speech2(String content,[String speaker]){
if(speaker==null){
print(content);
}else{
print(content +" by "+speaker);
}
}
2.3、把匿名方法作爲對象賦值給變量
一切皆對象,無論何時都要記住這句話。
void main(){
printFunc();
printFunc2("cmo");
}
//無參匿名方法
var printFunc=()=>print("無參匿名方法");
//有參匿名方法 參數名爲auther
var printFunc2=(auther )=>print("有參匿名方法,參數爲${auther}");
2.4、在其他方法中直接調用匿名方法或傳遞到其他方法
一切皆對象,方法就是一個Function 實例,List中的forEach 方法就是一個典型實現。
void main(){
var ls=[10,20,30];
print(addFunc(70);//輸出80
print(addList(ls,addFunc));//輸出[20, 30, 40]
print(addList(ls,(k)=>k+10));//輸出[30, 40, 50]
print(addFunc2("70"));//輸出 70ssss
}
/**
* @param list
* @param makeAdd(n) 方法對象作爲參數
*/
List addList(List list,makeAdd(n)){
for(var i=0;i<list.length;i++){
list[i]=makeAdd(list[i]);
}
return list;
}
//addFunc 用於保存方法對象 k 可以是任意合法的變量名,類型相當於dynamic
var addFunc=(k)=>k+10;
var addFunc2=(j)=>j+"ssss";
2.5、方法的閉包
方法的閉包從一定程度上來說就是返回值爲方法的Function 對象。
void main(){
print(addFunc2("70"));
Function createAdd(int m){
return (n)=>m+n;
/* 等價於上面的寫法
return (n) {
return m+n;
};
*/
}
//把閉包賦值給myAddFunc
var myAddFunc=createAdd(40);
print(myAddFunc);//Closure 'main_createAdd_closure'
print(myAddFunc(60));//輸出100
}
2.6、方法的別名
有點類似C++中的函數模板,方法別名相當於是把一組擁有相同方法簽名的方法的名字統一取了一個別名,各自獨立定義實現,在調用時動態改變指向即可。
void main(){
MyFun func=divide(88,4);//輸出22
func=minus(777,111);//輸出666
calculate(777,111,minus);//輸出666
}
//定義了參數爲兩個int 類型的方法原型,也可以定義在這些方法之後,位置不影響
typedef MyFun(int x,int y);
divide(int a,int b){
print('${a / b}');
}
minus(int a,int b){
print('${a -b}');
}
//也可以定義一個統一調用這些方法
calculate(int x,int y,MyFun fun){
fun(x,y);
}
2.7、抽象方法
要創建一個抽象方法很簡單隻需要使用分號“;”代替方法體,而且和Java 一樣抽象方法只能定義在抽象類中。
abstract class AbsEngine{
void init();//定義了抽象方法init
}
二、類
Dart 是一種面嚮對象語言,包含類和基於 mixin 的繼承兩部分,所有類都繼承自Object,每個對象是一個類的實例,並且構造方法中的參數也與普通方法一樣支持可選參數語法。
1、類的默認構造方法
與Java 類的默認構造方法一樣,如果不顯示聲明構造方法,那麼會默認生成一個無參數的構造方法,它將調用父類的無參數構造方法。
void main(){
Usr usr=Usr();
usr.name="crazymo";
usr.no="009";
usr.p();//crazymo:009
}
class Usr{
String name;
String no;
p()=>print("${this.name +":"+this.no}");
}
但是與Java 不同的是,Dart中只能有一個方法名稱與類同名的構造方法,以下的形式編譯的時候是會發生錯誤的。
class Usr{
String name;
String no;
Usr(){
this.name="name";
}
Usr(String name){
this.name=name;
}
Usr(this.name,this.no);
p()=>print("${this.name +":"+this.no}");
}
2、命名構造方法
Dart中的構造方法名稱不再侷限於類名,可以是形如類名.字符串格式的作爲構造方法的名稱。
void main(){
Usr usr=Usr("crazy","1001");
usr.p();//crazy:1001
Usr usr2=Usr.build("cmo","008");
usr2.p();//cmo:008
}
class Usr{
String name;
String no;
Usr.build(this.name,[this.no]);
Usr(this.name,this.no);
p()=>print("${this.name +":"+this.no}");
}
3、重定向構造方法
重定向構造方法只是使用了冒號 :調用同一個類中的其他構造方法。如果重定向的的那個構造函數的主體爲空,那麼調用這個構造函數的時候,直接在冒號後面調用這個構造函數即可。
class Usr{
String name;
String no;
Usr.build(this.name,[this.no]);
Usr(this.name,this.no);
Usr.make():this("crazy","mo");
Usr.create():this.build("crazyMo","66666");
p()=>print("${this.name +":"+this.no}");
}
4、構造方法的初始化列表
在構造方法體執行之前可以初始化實例參數,使用逗號分隔初始化表達式,非常使用用於final 變量的初始化。
void main(){
print(Rect(2,4).width);
}
class Rect{
final int width;
final int height;
final int square;
Rect(w,h)
:this.width=w,
this.height=h,
this.square=w*h;
}
5、調用父類的構造方法
- 父類的命名構造方法不會傳遞,如果希望使用父類定義的命名構造方法創建子類,則必須在子類中調用父類的命名構造方法
- 如果父類沒有默認的構造方法,則需要手動調用父類的其他構造方法。
- 調用父類構造方法的參數無法訪問this
- 在構造方法的初始化列表中使用super()時需要把它放到最後
void main(){
Child(88,66);//先後輸出 父類命名構造方法被調用、 子類構造方法被調用
Child.build(88,66);//先後輸出 父類命名構造方法被調用、 子類命名構造方法被調用
}
class Child extends Parent{
int x;
int y;
Child(x,y) :super.make(x,y){
//調用父類構造方法的參數無法訪問this
print("子類構造方法被調用");
}
//在初始化列表中使用super()時必須把它放到最後面
Child.build(x,y)
:this.x=x,
this.y=y,
super.make(x,y){
print("子類命名構造方法被調用");
}
}
class Parent{
int x;
int y;
Parent.make(x,y)
:this.x=x,
this.y=y{
print("父類命名構造方法被調用");
}
}
6、常量構造方法
類的屬性必須使用final 修飾且 構造方法使用const修飾。,事實上widget的構造方法就是使用這樣的形式。
void main(){
Rect2 rect=const Rect2(9,8);//必須定義了常量構造方法纔可以使用const 修飾
Rect2 rect2=const Rect2(9,8);
print(identical(rect,rect2));//true
}
class Rect2{
final int x;
final int y;
const Rect2(this.x,this.y);
}
7、工廠構造方法
使用 factory 關鍵詞修飾的構造方法就是工廠構造方法,但與其他構造方法不同必須使用new 創建實例而且在工廠構造方法內無法訪問this。工廠構造方法不會自動創建類的新實例,它可能從緩存返回實例或者返回子類型的實例。
void main(){
print(new Singleton().name);
}
class Singleton{
String name;
static Singleton _instance;
/*
factory Singleton([String name="cmo"]){
if(Singleton._instance==null){
_instance=Singleton._newObject(name);
}
return _instance;
}
*/
//更簡潔的寫法
factory Singleton([String name="cmo"]) =>
Singleton._instance ??=Singleton._newObject(name);
Singleton._newObject(this.name);
}
8、類的setter和getter
每個實例變量都有一個隱式的 getter 和 setter 方法,類似C#語法我們可以使用 get 和 set 關鍵詞來創建附加屬性,極大地提高了靈活度。
void main(){
Rectangle r=new Rectangle();
r.left=9;
r.width=10;
print(r.right);//如果不初始化left、width直接調用r.right會失敗
}
class Rectangle{
int left;
int top;
int width;
int height;
int get right=> left+width;//定義屬性right的get 方法,可以根據情況動態改變獲取right的規則
set right(value)=>left=value-width;//定義屬性right的set 方法
}
三、抽象類/接口
Dart中 無論是抽象類還是接口,都是使用abstract class 修飾,而且任何類都可以是接口,而且無論是接口還是抽象類都可以使用 extends 或者 implements 。
1、抽象類/接口的基本定義和用法
抽象類中與普通類的不同之處在於,抽象類中可以定義抽象方法,相當於是把方法的實現延遲到子類中。
abstract class Talk{
Talk(){}
void talk();
void speech()=>print("說");
}
2、抽象類/接口結合factory 實現工廠模式
void main(){
Run run=new Run("c");
run.run();//Car is running
run=new Run("b");
run.run();//Bus is running
}
class Bus implements Run{
void run()=>print("Bus is running");
}
class Car implements Run{
void run()=>print("Car is running");
}
abstract class Run{
factory Run(type){
switch(type){
case "c":
return new Car();
case "b":
return new Bus();
default:
return Car();
}
}
void run();
}
3、可調用類
所謂可調用類就是通過在類中定義一個名稱爲call 的方法從而實現可以讓類的實例可以像方法一樣被調用(具體的方法體就是call中的實現)。
void main(){
var clz=InvokeClass();//創建一個類的實例
var cls=clz("CrazyMo");//把實例名看成方法名進行調用
print("$cls");//輸出CrazyMo
print(clz.runtimeType);//InvokeClass
print(cls.runtimeType);//String
print(clz is Function);//false
}
//InvokeClass 就是可調用類
class InvokeClass{
call(String name)=>"$name";
}
4、extends 和 implements
兩者都可以用在abstract class 上,功能大同小異,最主要的區別在於使用implements 時子類必須重寫父類中的同名方法(無論同名方法是抽象方法還是普通方法),而使用extends 則不強制重寫。
//Robot2 編譯失敗XXXXXXXXXXXXXXXXXXXXXXXXXXXXX
class Robot2 implements Bot{
//必須重寫work方法,否則編譯失敗
}
class Robot extends Bot{
}
class Bot{
void work(){
print("bot");
}
}
5、mixins
mixins 是一種多類層次結構的類的代碼重用機制。要使用 mixins 只需要使用 with 關鍵字後面跟一個或多個類的名字即可,形如:class Child extends Parent with P1,P2 implements AbsParent。
5.1、mixin的典型用法
- 子類如果沒有重寫父類的同名方法時,如果2個或多個父類擁有相同簽名的方法,那麼子類會以繼承的最後一個超類中的方法爲準。
- 子類重寫了父類的同名方法時,則以子類的爲準。
5.2、mixin的繼承順序
void main(){
print(AB().getMessage());//B
print(BA().getMessage());//A
print(C().getMessage());//C
print(D().getMessage());//B
print(E().getMessage());//A
print(F().getMessage());//C
print(G().getMessage());//C
}
class A {
String getMessage()=>"A";
}
class B {
String getMessage()=>"B";
}
class P {
String getMessage()=>"P";
}
class AB extends P with A,B {
}
class BA extends P with B,A {
}
class C extends P with B,A {
String getMessage()=>"C";//如果子類定義了同名方法會把其父類的都覆蓋掉
}
//在mixin中 implements 只是表面了要實現A的方法,並不會把前面的with 的覆蓋掉
class D extends P with B implements A {
}
class E extends P with A implements B {
}
class F extends BA with C{
}
class G extends C with BA{
}
四、泛型
Dart從1.21開始支持泛型,可以在方法的返回值類型、參數類型、局部變量類型上使用泛型,絕大部分語法都與Java 大同小異,也支持使用extends 進行泛型限制。
void main(){
var key=addCache("Crazy","Mo");
print(key);
}
K addCache<K,V>(K key,V value){
K tmp=key;
V tmpV=value;
return tmp;
}
class Message <T extends AbsMessage>{
final T msg;
Message(this.msg);
}
重要的區別在於Java的泛型只存在於編譯期,運行時會被自動擦除,而Dart中的泛型是固化的,不會被擦除,運行時也可以判斷泛型的類型
void main()
var list =List<String>();
print(list is List<String>);//true
print(list is List<int>);//false
print(list.runtimeType);//JSArray<String>
}
五、庫
Dart中是使用import URI (URI全稱 Uniform Resource Identitifier 統一資源標識符)統一形式關鍵字進入導入庫的,一個Dart源文件可以看成一個庫,也可以把多個Dart文件看成一個庫。
1、import dart:scheme 導入Dart 內置的系統庫
其中scheme 爲庫的名稱
import "dart:math";
import "dart:async";
2、import package:scheme 導入第三方開源庫
Dart 提供了一個Dart Packages的開源庫,通過引入其中的庫可以加快開發速度,與原生Android的引入步驟類似:
- 首先打開Dart Packages,查找到相應的庫,點擊進去並切換到Installing 頁籤
- 打開項目的pubspec.yaml 文件並在dependencies引入依賴
- 安裝下載第三方庫可以通過命令行執行flutter packages get或者圖形界面的Package get 按鈕
$ flutter packages get
- 經過以上兩步之後就可以在Dart中導入指定的庫並使用了。
import 'package:image_picker/image_picker.dart';
3、import 文件路徑 導入項目中本地庫
- import 文件路徑 完全導入
- import 文件路徑 show 類名,類名 只導入指定類名的
- import 文件路徑 hide 類名 導入除了指定類名的意外的所有類
- import 文件路徑 as 庫別名 導入庫指定別名
import 'core/mylib.dart' ;//完全導入
import 'core/mylib.dart' show Test;//只導入Test類
import 'core/mylib.dart' hide Test;//導入除了Test以外mylib.dart中的所有類
import 'core/mylib.dart' as lib1;//lib1.Test
import 'core/mylib2.dart' as lib2 ;//lib2.Test
- import 文件路徑 deferred as 庫名 延遲導入,按需加載
比如說我在libs/core目錄下有一個文件名爲lazyload.dart,需要延遲加載就可以按照以下的方法,先加載再使用
import 'core/lazyload.dart' deferred as lazylib;
void main(){
new Future(()=>lazyLoad()).then((_)=>lazylib.MyLazyLoad().testLazy());
}
//執行加載
lazyLoad() async{
await lazylib.loadLibrary();
}
- part 多文件分庫