引言
前一篇文章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 多文件分库