likes
comments
collection
share

flutter-dart编程语言

作者站长头像
站长
· 阅读数 5

前言

若想开始开发 flutter,那么谷歌推出的 dart 编程语言是必备的, dart基本上就是为学习java 的人量身订做的,在一些语法上有比较像 swift,可以说谷歌为了推出跨平台flutter,是花了不少心思的,毕竟一个是土生土长的java,另一个也是ios端目前最新最流行的swift

从语言上来说,javaswift只要会一个,上手就和喝水一样简单,下面跟着我学习一遍吧,学完剩下的就是实战了

dart_demo案例 -- 此案例可以在vscode下载code runer插件,进入代码后右键运行(注意:从文件的main函数运行)

学习参考地址

ps:可能有人会说,你这不就是照着参考地址,学完一遍后,自己举个例子简单翻译就结束了么?我想说,这就是我学习理解与记忆的一部分呀,另外也可以看出 dart 语言上手就是这么简单

dart 编程语言

导入一个库

导入库的时候使用 import,后面一般是 [dart:库名],一般系统会自动导入

另外对于引入自己的文件,则是根据当前路径定位文件的相对位置

./: 返回当前文件所在目录

../: 返回上一个目录

//使用库,必须要写到最上面
import 'dart:async';
import 'dart:html';
//导入数学库
import 'dart:math';
//导入内置的文件,导入某个项目文件,使用相对路径
import './4class.dart'; 

//导入一个库,由于其调用方式和c类似,因此可能会和我们项目中有重名的情况
//为了避免调用错误或者冲突报错问题,可以使用 import * as + 名字 的方式导入,即模块化导入
//默认导入方式
import 'dart:convert'
//模块化导入方式
import 'dart:convert' as convert;
//以这种模块化方式导入,相当于将里面暴露出来的方法给放到了自己命名的类中
//import + 库名 + as + 命名,调用方式: 类名.函数名即可调用
原本调用: jsonEncode()  ---  模块化调用: convert.jsonEncode()

常量和变量

变量都是 var修饰,很多语言常量用 let 修饰常量,这里的 final 就相当于 let,可以给变量赋值一次, const表示只读,一般在编辑阶段使用,定义时就要给值,也可以像c一样使用指定类型声明

dart在声明对象时,无需指明变量类型,系统会自动根据创建时的类型,推断出实际类型,即类型推断(作为参数传递时,则需要指明);在需要时,可以将 let之类的修饰符换成指定类型,以声明变量类型,也算是保留一些其他端的用户习惯

ps:和 js不同,jsvar具备变量提升效果,let 相当于声明普通的局部变量,这里的 var 声明的就是一个普通变量

//赋予一个字符串属性
const readonlyName = "我是只读的常量"; //编辑期间使用,  也和 swift 中 let 类似

// final finalName = "我只能被赋值一次";//只能被赋值椅子,之后不能赋值
final finalName; //只能被赋值一次,之后不能赋值,宛如swift中的let
finalName = "我只能被赋值一次";

// static const sreadonlyName = "我是只读的类属性,只能在类中使用";
var name = "我是可更改的变量"; //变量
name = "我的内容更新了";

//也可以使用声明指定类型方式接收变量,平时用的比较少
String a = "我是直接使用类型的方式声明";

数字number

这里的数字其实也是对象,和 js 类似,数字有自己的一些对象方法,能进行一些数学操作

ps: 数字的类型都是小写哈,和其他的不同,感觉可能是另外一个人写的😂

//数字,可以使用常见的Math函数
var num1 = 10; //类型推断,实际是int类型
var num2 = 10.2; //类型推断,实际是double 类型

//数字可以使用abs()、ceil()、floor()等,也支持常见的四则运算、移位、与、或、非、异或等操作
//也可以使用math数学库 import 'dart:math';
var num3 = num1 | num1 & num1 & num1;
var numa = num2.abs();
var numc = num2.ceil();
var numf = num2.floor();
var nump = pow(num2, num1); //数学库的方法

//bool类型
var boolNum = true;
boolNum = false;

字符串String

字符串的类也是一个对象,可以像其他语言一样通过 + 或者${} 拼接字符串,连一些字符串的操作名字都一样,如下所示,列出一部分

var str = '我是一个普通的字符串';
var str1 = "我是一个没区别的双引号字符串";

var str2 = str + str1; //拼接字符串

//几可以使用三个单引号或者三个双引号,将一片多行字符串拼接起来
var str3 = ''' 
'sfasdf' 
'123123'
''';
str3 = """ 
sfasdf
123123
""";

//字符串前面加上r内部字符串不会被转义
var rstr = r'1212313 \n 123123';

//字符串中拼接其他变量,通过 ${} 包裹变量内容
var spitStr = "str的总长度是:${str.length}, str1的内容是:${str1}";

//获取子串 1-2,从1开始共 3-1=2个字符串
str.substring(1, 3);

//分割字符串为数组,其他语言中也很常见
str.split('一');

//是否包含某一个子串
final result = str.contains(str1);

//查找子串的位置
str.indexOf(str1);

//转化为大小写
str.toLowerCase();
str.toUpperCase();

字符串数字相互转化

字符串,数字中比较常见转化了,如下所示

其中 toString是各个对象中基本都存在的方法,可以转化字符串,在一些网络数据中,为了安全基本基本必调的

保留小数点也是比较常见的,有指定的方法也能避免自己写逻辑

var num1 = 10;
var num11 = 10.21312893192783;
var str1 = "10.2";

//数字转化字符串
var num2 = int.parse(str1);
var num3 = double.parse(str1);

//字符串转化成数字
var str2 = num1.toString();
var str3 = num1.toStringAsFixed(2); //保留小数点后两位

数组 List

数组在这里类型名字为 List不是Array), 不得不说,使用中最常见的集合就是数组了,其为有序的线性集合

//创建一个空数组,默认为任何类型,构造方法的形式,系统不推荐
var list = [];
var list1 = <int>[]; //创建只有int类型的数组

//添加元素
list.add(1);
list.add("asfsdf");
list.add("123");
list.addAll(list1); //添加数组内所有元素

//删除指定元素,或者指定索引的元素
list.remove('123');
list.removeAt(0);

//插入的元素
list.insert(1, "插入的元素");

//获取数量
list.length;

//内部排序
list.sort((a, b) => a > b);

//将数组元素使用分隔符拼接成字符串
var str = list.join(';');

list.reversed; //置反数组

//forin遍历数组(推荐)
for (var item in list) {
}
//内部使用的也是forin
list.forEach((element) {
});
var newList = list.map((e) => {
  e = e + "啦啦啦"
});

哈希表 Set

Set是一个哈希表,为一个无序集合,其根据内容生成哈希值,使用哈希值作为索引获取数据信息,内部具体表现是哈希表树表哈希表树表结合还是其他,就不知道了(据说一些在使用哈希表同时,采用二分法的方式修改查询,放弃了红黑树等树表)

特点:写入速度稍慢,读取速度快,不会存放重复数据

//创建一个 set
var sets = {};
//使用Set的构造方法创建
var sets1 = Set();
var sets2 = Set<String>();

//创建一个有值的set
var set = {1, 2, 3, '哈哈', '啦啦'};
var set1 = <int>{1, 2, 3}; //创建一个指定类型的Set集合

set.add(3);
set.add('哈哈');

//移除某一个元素
set.remove(3);

//是否包含某一个元素,此时取出已经没必要了
set.contains(3);

set.length; //获取数量

//遍历集合(推荐)
for (var item in set) {
}
//内部使用的也是forin
set.forEach((element) {
});

key-value哈希表 Map

Map也是一个哈希表,只不过是以key生成哈希值,存放value等信息,即 key-value形式的哈希表,实现与Set相似,内部具体表现是哈希表树表哈希表树表结合还是其他,就不知道了(据说一些在使用哈希表同时,采用二分法的方式查询,放弃了红黑树等树表)

//创建一个空Map,注意{}代表空Set
var map = <String, String>{};
//使用Map的构造方法创建
var map1 = Map<int, String>();

//快捷创建一个Map集合
var map2 = {
"key1": '哈哈',
"key2": "啦啦"
};

//赋值和获取
map["123"] = "啦啦";
var content = map["123"];

//获取另一个类型的数组,注意下标是key,而不是数组的顺序索引
map1[1] = "哈哈";
var content2 = map1[10];

//是否包含某一个元素key
map.containsKey("key");

//遍历集合(推荐)
for (var item in map.keys) {
}
//内部使用的也是forin
map.forEach((key, value) {
});

任意类型 dynamic、Object

dynamic表示任意类型,可以作为一个通用类型(实际使用中,建议能不使用就不适用,容易出现隐性bug),就像typescript 中的 any 类型,java 中的 Object 类型

Objectdynamic类似,可以表示所有的对象类型,就目前所知道的,String、number等都是对象类型(还没有碰到其他非对象类型)

tipsios中的 idNSObject类型就不一样了,那里的 int,char等都是非对象类型的,因此不能代替

基本流程语句

基本流程语句包括常见的 if elsefor循环while循环do while循环switch控制语句

var a = 10;
var b = 20;
var c = "a";
var d = "b";

if (c == d ) {
//满足条件
}else {
//不满足条件
}

//for 循环遍历
for(var i = 0; i < 10; i++) {
print("哈哈哈");
}

//for in快速遍历集合
var list = [];
for(final item in list ) {
print(item);
}
//内部是for in
list.forEach((element) {
print(element);
});

//先走控制语句
while (a++ > b) {
print("a不大于b");
};

//先走内部语句,再走控制
do {
print("a不大于b");
} while(a++ > b);


var command = 'open'; // on、open、off、close、other
switch(command) {
case 'on':
case 'open': 
  print('门开着的');
  break;
case 'off':
case 'close': 
  print('门关着的');
  break;
default: 
  print('门可能被打破了,不知道算开,还是关');
  break;
}

异常捕获try catch

编写代码时难免会碰到不可抗拒因素,例如:一些老的api在使用中会 throw 一个异常,或者网络请求的失败可能也会抛出一个失败异常,此时都可以使用 try-catch解决,如果想捕获指定异常,可以使用 on 关键字来解决

void test9(int num) {
  if (num > 10) throw FormatException("num不能大于10");
}
void test10() {
  try {
    test9(12);
  }catch(e) {
    print('出现异常了:${e.toString()}');
  }

  try {
    test9(12);
  }on FormatException {
    print("根据抛出的异常类型,只捕获某一类");
  }catch(e) {
    print("捕获剩余的类型");
  }finally {
    //无论是否异常都执行
  }
}

定时器

定时器在平常开发中使用在常见不过了,这里就直接先介绍了

下面就是使用Timer写的一个延迟定时器间隔性定时器

注意:间隔定时器使用完后要合适时机释放,否则会一直执行(合适时机可以是,某一个功能执行完毕了、此模块推出了等等)

//只会回调一次的定时器
Timer(Duration(seconds: 5), () {
//执行一个5s的定时器
});

//会回调多次的定时器
var num = 0;
Timer.periodic(Duration(seconds: 5), (timer) { 
  if (num++ > 10) {
    timer.cancel(); //取消定时器
  }
  print(num);
});

函数

函数的定义

函数的定义和 java 很像,如下所示

//无参无返回值函数
void getUserInfo() {
}
//无参的可简化返回值效果
//注意下面返回值实际默认是 dynamic
//getUserInfo1() {
getUserInfo1() {
}

//带参的函数
getUserInfoByToken(String token) {
}

//参数带?的函数,可以理解为可选参数,除了指定类型,可以传null
getUserInfoByToken1(String? token) {
}

//带参带返回值
List getUserInfiByToken1(String token) {
  return [];
}
//设置返回类型
List<String> getUserInfiByToken2(String token) {
  return ["啦啦啦"];
}

闭包(匿名函数)

在开发中,不是只会用到平常的函数,有时候使用闭包函数,将会减少工作量,且使用起来更加方便,无论是传参还是调用

//无参无返回值闭包函数
var block = () {
print(123);
};
block();

//带参有返回值的
var block1 = (num) {
return num + 1;
};
var result = block1(10);
print(result);


//带箭头的,简化操作,有点兼容js箭头函数的意思
var block2 = (num) => pow(num, 2);
var result2 = block2(14);
print(result2);

var block3 = (str) => {
print("哈哈哈:${str}")
};
block3("哎呀");

闭包、函数作为参数(两者一样)

闭包函数作为参数时,和正常函数一样,只要是一眼类型的都可以,传入的都是函数指针,或者闭包指针

如下所示,演示了无参函数,带参函数、闭包、普通函数之间的传递方式,其中也用 ? 表示了可选类型的声明传入

?: 为可选类型,代表指定类型或者null,例如 double? 代表的是一个浮点数 或者 null

//无参普通函数
getUserInfo() {
}

//传入无参闭包或者普通函数
requestUser(void completed()) {
  Timer(Duration(seconds: 2), () {
    completed();
  });
}
//传递闭包参数,加上?表示可选,(闭包的参数名在中间,因此?写到最后)
requestUserTokenByCompleted(void completed(String? userToken)?) {
  Timer(Duration(seconds: 2), () {
    if (completed != null) {
      completed(DateTime.now().microsecondsSinceEpoch % 5 > 2 ? 'abc' : null );
    }
  });
}

//flutter中标准写法改进
requestUser(Function() completed) {
  Timer(Duration(seconds: 2), () {
    completed();
  });
}
//可选带参类型案例
requestUserTokenByCompleted(Function(String? userToken)? completed) {
  Timer(Duration(seconds: 2), () {
    if (completed != null) {
      completed(DateTime.now().microsecondsSinceEpoch % 5 > 2 ? 'abc' : null );
    }
  });
}

//闭包函数
void main() {
    //调用无参闭包函数
    requestUser(() {
      print("调用了一个无参闭包函数");
    });
    //此时requestUser的回调在 getUserInfo 普通函数中
    requestUser(getUserInfo);
    
    //调用带参闭包函数
    requestUserTokenByCompleted((token) {
      print("获取用户信息成功了,token:${token}");
    });

    requestUserTokenByCompleted((token) {
      print("获取用户信息成功了,token:${token}");
    });

    requestUserTokenByCompleted(null);
}

重名函数类型:在传递函数或者闭包函数的过程,有时候会显得类型非常繁琐,可以采用重命名的方式来简化参数类型,简化都的参数和普通类型一样,例如:double


//重命名,或者声明一个闭包类型,与平常的不一样名字就在返回值和参数中间,作为参数也一样
typedef void blockType(int num);

//像普通变量一样调用
test(blockType block) {
    block(2);
}

运算符

赋值运算符

赋值运算符,在很多语言中都比较通用,例如 ??= *= /= += -= %= &= |= ~= <<= >>= >>>=

&=: (按位与赋值) |=: (安按位赋值) !=: (按位取反赋值) <<=: (按位左移赋值) >>=: (按位算术右移,最前面位置补零) >>>=: (按位逻辑右移,最前面位置补符号位)

这里主要介绍不常见的的一些

//??
var a = b ?? c //当 b存在时返回b,否则返回c

//??=
b ??= value  //如果b存在,b保持不变,相反,b为空,则将value的值给b

关系运算符

关系运算符,比较常见的就是 as、is、is!==判断就不多说了,是否相同

as: 类型转换,可以用来转化类型,例如:将声明父类的转化声明成子类类型(实际如果不是这个类型使用可能会报错)

is: 判断一个类是不是某一个类或者其子类,是则返回true,否则返回false;如果一个类实现了某一个接口 T,那么 is T 也是true

//as、is、is!
//有这么两个类学生Student继承人类Person

//如下,Student继承自Person,虽然强转指定类型,编译阶段不一定报错,实际运行没有该方法还是会报错
var p = new Person();
var a1 = p as Student; //
a1.getName(); //如果 p实际上没有 Student的 getName方法,那么就会报错

//as
// 只是 强转了编译期的声明类型,编译期间不会报错了
//实际调用时,如果不是实际类仍然可能会报错
//使用时,一般通过多态或者通用类型声明一个对象,可以使用as转化为实际类型使用

//is 
//判断一个类是不是某一个类或者其子类,是则返回true,否则返回false
//如果一个类实现了某一个接口 T,那么 is T 也是 true

级联运算符

..?.. 为级联运算符的两个关键字,前者是默认调动,后者是可选调用,为空则不调用

//.. 或者 ?.. 可以省略调用,简化多次调用方法或者变量
//对于对象存在的情况,使用..
var paint = Paint()
    ..color = Colors.black
    ..strokeCap = StrokeCap.round
    ..strokeWidth = 5.0;

//相当于

var paint = Paint();
paint.color = Colors.black;
paint.strokeCap = StrokeCap.round;
paint.strokeWidth = 5.0;


//队友有些对象可能不存在的情况,使用?.. + ..
querySelector('#confirm') // Get an object.
    ?..text = 'Confirm' // Use its members.
    ..classes.add('important')
    ..onClick.listen((e) => window.alert('Confirmed!'));

//相当于

var button = querySelector('#confirm');
button?.text = 'Confirm';
button?.classes.add('important');
button?.onClick.listen((e) => window.alert('Confirmed!'));

异步支持 async await

这里的异步支持就是比较常见的 async、await

其中 async 则是让一个函数开始支持异步操作,此时可以理解为并没有开启异步线程,当异步方法碰到await时,此异步方法开始被阻塞,并保存到Future对象中,放到队列中延迟执行,然后切出此函数,继续往后执行(注意:不是跳过await继续执行该函数,而是返回上一级继续执行),然后继续执行,碰到await修饰的继续阻塞保存放入队列,直到其他方法执行完毕之后,然后开始制定Future延迟队列中阻塞的内容,就这样模拟出了异步效果(由于flutter是单线程,又根据效果猜想,大致原理应该相似,不影响使用)

ps: 阻塞执行这一步实际有没有开出多线程可以自己查看一下,可能仅仅是模拟异步逻辑,类似操作系统切换cpu执行任务片段时间等,以让任务获得响应

另外,FuturejsPromise类似,只不过没有resolve、reject,默认返回 Future参数,通过await或者 then都可以获取参数,只不过使用 await 的前提是,当前函数得异步支持(async),而 then 不需要(另外可以结合 Completer做出类似 Promise的效果,后面会有文章介绍)

具体为什么这么说,等看完测试结果就能猜出一些了

//声明一个异步方法
Future<bool> simulateOperate() async {
  //假设这一有一些非常耗时的操作逻辑,例如对本地数据整理
  var num = 0;
  for(var i = 0; i < 10000000; i++) {
    num++;
  }
  print('我的异步任务执行完毕了--${num}');
  return true;
}

//执行一个同步方法调用,会发现,此异步任务执行正常
void test() {
  print('开启test同步方法');
  //通过打印可以看到这里返回的是函数指针,异步任务结果这里已经忽略了
  //没有 await,实际阻塞了后续任务的执行,可以理解我同步执行
  var res = simulateOperate();
  print('我是test同步方法,执行完毕了:${res}');
}

//必须在有async的函数下使用 await,能够等待异步任务执行结果,另外没有返回值时可以省略 Future
testAwait2() async {
  print('开启testAwait2异步方法');
  var res = await simulateOperate(); //之而立返回的是true
  print('我是testAwait2异步方法方法,执行完毕了:${res}');
}

//和上面的一样,只不过时使用 then
testAwait2() {
    print('开启testAwait2异步方法');
    simulateOperate().then((res) {
        print('我是testAwait2异步方法方法,执行完毕了:${res}');
    })
}

void main() {
  print("总体任务开始");
  test();
  print("第一个测试完毕");
  testAwait2();
  print("第二个测试完毕");
}

执行结果如下所示

//打印结果
// I/flutter ( 5664): 总体任务开始
// I/flutter ( 5664): 开启test同步方法
// I/flutter ( 5664): 我的异步任务执行完毕了--1000000000
// I/flutter ( 5664): 我是test同步方法,执行完毕了:Instance of 'Future<bool>'
// I/flutter ( 5664): 第一个测试完毕
// I/flutter ( 5664): 开启testAwait2异步方法
// I/flutter ( 5664): 我的异步任务执行完毕了--1000000000
// I/flutter ( 5664): 第二个测试完毕
// I/flutter ( 5664): 我是testAwait2异步方法方法,执行完毕了:true

看了这个结果应该知道怎么回事了吧,需要 await 的时候在声明 async函数,否则不但没有效果,还徒增代码

tips: 如果需要同时 await 多个函数或者网络请求,那么可以使用 Future.await(Future1, Future2, ...)

Future.wait([fun1(), fun2(), fun3()]).then((List resList) {
    print(resList); 
}).catchError((error) {
    print(error);
})

枚举

一般用在类型判断是使用,其能更好数字number类型,能友好的判断出类型的意义,提高代码可读性,且配合 switch 使用更方便,还能更好给出提示

//枚举
//调用,可以使用前面介绍的switch来实现不同情况,当然if else也可以
enum Level {
  firstLevel,
  secondLevel,
  thirdLevel,
  maxLevel
}

//枚举的使用
Level.firstLevel

类和对象

类是对象的抽象,对象是类的载体,可以理解为对象的模板规范,可以通过这个模板规范生产出相似的对象(不多解释,抽象的事情需要案例才能更好理解)

类的声明

声明一个类用 Class 关键字

//通过 class 关键字声明一个类,叫Person,为对人的部分抽象
class Person {
   String name = '';
   int age = 0;
   
   Person(this.name, this.age);
}
//通过 Person 定义出 张三和李四 两个人,这两个人(personA、personB)就是对象
var personA = Person("张三", 20);
var personB = Person("李四", 21);

属性

属性声明时,需要默认赋予初值,否则就设置为可选类型?,可选类型默认为 null

class Point {
  double x = 0;
  double y = 0;
  // double? x, y; //可以连续声明多个,也可以赋值
  // double x = 0, y = 0;
  final double z; //属性只能被赋值一次
}

构造函数

构造方法有多种创建方式,下面就一一介绍(注意构造方法只能有一个,命名式除外),而析构函数却没看到😂

注意: 如果不写构造方法,会有默认的无参构造方法(所以系统才要求所有参数必须有默认值,如果重写构造方法,那初值就一定必要了)

class Point {
  double x = 0;
  double y = 0;
  final double z; //属性只能被赋值一次

  一般构造方法这么写
  Point(double? x, doubel, y) {
    this.x = x;
    this.y = y;
  }

  //系统给了语法糖就可以这么简化了, 注意,构造方法不可以被重写,只能写自己的
  Point(this.x, this.y, this.z); //构造方法

  //赋值的时候,以这种方式可以给剩下的赋值
  Point(this.x, this.y): z = 20;

  //命名式构造方法
  //方式: [类名].[方法名]
  //如果构造方法不传参数,或者漏传,需要在后面加上:对剩下参数赋值,如下所示
  Point.origin(): x = 0, y = 0, z = 0;
  //相当于下面的
  Point.origin(this.x, this.y, this.z);

  //factory 工厂模式
  //先使用命名式构造方法,定义一个构造函数,用来赋值
  Point.internal(this.x, this.y): z = 0;
  //在使用默认的属性,来给默认的构造方法开启构造工厂
  factory Point(String key) {
    var pointList = pointMap[key];
    if (pointList == null) {
      return new Point.internal(0, 0);
    }
    return Point.internal(pointList[0], pointList[1]);
  }
}

各种类的的调用如下所示

void main() {
  var point0 = Point(0, 0, 0); //正常使用构造方法
  var point1 = Point.internal(10, 10); //命名式构造方法
  
  var point = Point("1010"); //调用工厂构造方法初始化
  point.printPoint(); //调用对象方法
  Point.sPrintPoint();//调用对象方法
}

静态方法

在一个类中声明静态方法,需要使用 static 关键字,这样该方法就可以通过类名调用

注意: static 的方法中,this 不是指向类,因此static的不能使用this

class Point {
  //声明一个函数,这里不多介绍
  printPoint() {
    print("point");
  }

  //可以理解为类方法,需要通过类名调用,但是也不像其他语言一样,其不能与对象方法同名
  static sPrintPoint() {
    //注意static方法不想像其他语言一样使用this调用同类型方法
    print("point");
    Point.sPrintPoint2(); //不能使用this.sPrintPoint2()
  }
  static sPrintPoint2() {}
}

重写运算符

通过重写运算符,可以让一个类具备和数字一样的运算

声明方式: Point operator 运算符符号 + 参数 + 实现

class Point {
  double x = 0;
  double y = 0;
  
  //重写运算符,可以传入操作符右侧变量
  Point operator +(Point value) => Point.origin(x + value.x, y + value.y);
  Point operator -(Point value) => Point.origin(x - value.x, y - value.y);
}

Setter和Getter

这里的 Setter、Getterios中的就大有不同了,ios是真的重写属性的SetterGetter方法,这里的SetterGetter 只是能像属性一样调用罢了

其一般用来根据当前属性虚拟出新的属性,平时使用不是必要的(也可以搞出类似属性的单例)

定义通过 get set 关键字实现,如下所示

其只能以 set 和 get 修饰方法名,其能像方法属性一样赋值和获取,但不是重写已经声明的属性字段,也不能同名
var instance = Rectangle(1, 2, 3, 4);
class Rectangle {
  double left, top, width, height;

  //可以通过static静态方法,搞一个单例
  static Rectangle get shared => instance;

  //普通使用set 和 get,重写后调用方便 看情况使用
  //下面设置一个居右侧属性
  double get right => width + left;
  set right(double value) => left = value - width;
}

抽象方法(接口与实现)

抽象方法通过 abstract关键字修饰,其就是接口,通常作为接口实现成多个相似的类

一个类实现一个接口,通过 implements 关键字即可,实现多个接口通过,隔开

//抽象方法 abstract
//其无法创建实例,且内部定义的方法一定是抽象方法
abstract class AbstractClass {
  speak(); 
}
abstract class AbstractClass1 {
  talk();
}
//实现接口
//通过实现接口,可以给让不同的类实现统一调度功能,就像多态一样
class Children implements AbstractClass {
  @override
  speak() {
    print("嘤嘤嘤");
  }
}
class Person implements AbstractClass {
  @override
  speak() {
    print("我不止嘤嘤嘤,我还哈哈哈");
  }
}

通过将变量类型声明成接口类型,可以被遵循接口的类统一调度,如下所示

speakForAll(AbstractClass p) {
  p.speak();
}
testSpeak() {
  var chi = Children();
  var per = Person();
  speakForAll(chi);
  speakForAll(per);
}

继承

通过 extends 关键字,可以继承一个类,被继承的类被称为 父类、超类,继承者被称为子类、派生类

//class 内部也可以定义接口,只不过是通过继承重写的方式来实现
//因此通过继承定义的接口也被称为隐式接口
class ParentClass {
  ParentClass();

  void sayHello() {
    print("hello");
  }
}
//继承,采用 extends
class SubClass extends ParentClass {
  SubClass();

  @override
  void sayHello() {
    // super.sayHello(); //可以通过super调用父类方法
    print("我不会sayHello");
  }
}

父类型可以用来声明子类变量,这就是多态的一种

一个父类(超类)被继承后,生成多个种类,这也是多态,例如:定义一个动物类为父类,子类为牛、马、蛇、羊,这也是多态

//这就是多态的使用之一
test(ParentClass p) {
    p.sayHello();
}
var sub = SubClass();
test(sub)

//这也是多态,子类也被成为派生类
class Animal {
    speak() {
        print("我会说话");
    }
}
class Dog extends Animal {
    @override
    speak() {
        print("汪汪汪");
    }
}
class sheep extends Animal {
    @override
    speak() {
        print("咩咩咩");
    }
}

继承并实现抽象接口

继承和实现接口并不冲突,且接口能同时实现多个,通过,隔开,如下所示

//继承并实现抽象接口,可以同时实现多个抽象接口
class B extends A implements AbstractClass, AbstractClass1 {
  @override
  speak() {
    print("我不止嘤嘤嘤,我还哈哈哈");
  }
  
  @override
  talk() {
    print('我已经不只是会嘤嘤嘤,还会正常聊天了');
  }
}

类扩展 extension

能在原有类的基础上扩展新的属性或者方法,扩展的父类,子类仍然能用,反之不能

使用方式: extension + 扩展名 + on + 被扩展类名

ps:可能有人觉得扩展名没有存在必要,实际扩展名可以作为标记名,将不同功能的扩展更好区分开,更利于阅读

extension Employee on SubClass {
  void work() {
    print("我能工作了");
  }
}
extension Sleep on ParentClass {
  void sleep() {
    print("我休息了");
  }
}

多继承 maxin

使用maxin修饰的父类,可以让继承者们同时继承实现多继承的效果

一般类只能extends一个父类,通过 with + maxin声明类,可以实现多继承,且 maxin 修饰的类只能使用with关键字继承

被多继承父类声明方式: maxin + 类名

继承时使用方式: class ... + with + maxin类(多个,隔开)

多继承的好处不少,可以多个小功能类拼接成一个大类使用,使用非常灵活(比起扩展来说),缺陷也非常明显,太灵活了,代码不好控制,可读性下降

注意:其他的语言通过接口、协议、扩展等代替它(C++除外),扩展与其类似,而扩展却是针对固定类的

//定义一个打游戏的类
mixin Player {
  bool canPlayWangzhe = false;
  bool canPlaychiji = false;

  playGames() {
    print('在这里,我是一名游戏玩家');
  }

//继承 A 的同时,继承了 Player,通过with可以继承多个,隔开
class maxinTestClass extends A with Player {
}
多继承类型约束

类型约束,即:maxin修饰的类被多继承时,只有继承了指定类,才能多继承他

声明被多继承父类方式: maxin + 类名 + on + 被约束到类名

//定义一个打游戏的类
mixin Player {
  bool canPlayWangzhe = false;
  bool canPlaychiji = false;

  playGames() {
    print('在这里,我是一名游戏玩家');
  }
}

//定义一个类,只能被继承B的类,多继承
mixin stu on B {
  study() {
    print('我是学生,要学习');
  }
}

//继承 A 的同时,继承了 Player,通过with可以继承多个
//继承了A,此时不能继承Stu
class maxinTestClass extends A with Player {
}

//继承多个,继承B的同时继承Stu,player
class maxinConstraintTest extends B with Player, stu {
}

泛型

泛型可以通过一个类型 name (一般用T,也可以根据指定场景起名),可以泛指某一个设定的类型(用户传入的泛型类型)

示例:List集合,其可以设定一个指定类型,设定后,添加所有元素都只能是这个类型(这就使用泛型改装一个类)

var list = <int>[];//这个<int>,括号里面的就是泛型,也可以点进去查看,int就是用户传入的泛型类型
使用泛型改装类

在类名后面加上一个<T>的形式,可以指定一个泛型,通过该泛型,可以用来统一参数类型

//下面定义一个栈,定义泛型(命名为T),用来代替里面存放节点的类型
class Stack<T> {
  List<T> container = [];
  //传入 T 类型的参数
  push(T element) {
    container.add(element);
  }
}

调用如下,一个传入了String 类型,即 TString,另一个传入了 int, 即 Tint

var stack = Stack<String>();
stack.push("哈哈");
var stack2 = Stack<int>();
stack2.push(2);
使用泛型改装函数

在开发中,可能会封装一个支持多种类型的处理方法,例如:往一个集合塞东西,交换两个参数内容等等

//可能会封装一个,支持多个类型的处理方法
//单纯的使用一个泛型方法,其类型约束也是extends
class handlerClass {
  static pushByList<T>(List<T> list, T object) {
    list.add(object);
    //debug可以打印了运行时的实际类型字符串
    // print(object.runtimeType.toString());
  }
}
泛型约束

通过 extends 来约束一个泛型,因此也可以理解为,此泛型继承某个类、接口

//如果想 像多继承一样限制泛型可以使用的类型怎么办
//如下所示,使用 extends 来限制类型为 int 或者为其子类
class StrStack<T extends String> {
  List<T> container = [];
  //传入 T 类型的参数
  push(T element) {
    container.add(element);
  }
}

//泛型约束定义一个函数
class handlerClass {
  static pushByList<T extends String>(List<T> list, T str) {
    list.add(str);
  }
}

调用方法如下所示

var stack = StrStack<String>();
stack.push("哈哈");
// var stack2 = StrStack<int>();这个就报错了,函数也是一个样子

var list = <String>[];
handlerClass.pushByList(list, "哈哈哈");

最后

跟着这篇文章,自己又学习扩展了一次,收益匪浅,也算是一份常用内容记录,如果又看到了,跟着走一遍,相信也会有所收获,至少对 dart 不在陌生不是 😂