flutter-dart编程语言
前言
若想开始开发 flutter
,那么谷歌推出的 dart
编程语言是必备的, dart
基本上就是为学习java
的人量身订做的,在一些语法上有比较像 swift
,可以说谷歌为了推出跨平台flutter
,是花了不少心思的,毕竟一个是土生土长的java
,另一个也是ios
端目前最新最流行的swift
从语言上来说,java
和swift
只要会一个,上手就和喝水一样简单,下面跟着我学习一遍吧,学完剩下的就是实战了
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
不同,js
的 var
具备变量提升效果,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
类型
Object
和 dynamic
类似,可以表示所有的对象类型
,就目前所知道的,String、number等
都是对象类型(还没有碰到其他非对象类型)
tips
:ios
中的 id
和 NSObject
类型就不一样了,那里的 int,char等都是非对象类型的,因此不能代替
基本流程语句
基本流程语句包括常见的 if else
、for循环
、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执行任务片段时间等,以让任务获得响应
另外,Future
和js
的Promise
类似,只不过没有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、Getter
与ios
中的就大有不同了,ios
是真的重写属性的Setter
和Getter
方法,这里的Setter
和 Getter
只是能像属性一样调用
罢了
其一般用来根据当前属性
,虚拟出新的属性
,平时使用不是必要的(也可以搞出类似属性的单例)
定义通过 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
类型,即 T
为 String
,另一个传入了 int
, 即 T
为 int
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
不在陌生不是 😂
转载自:https://juejin.cn/post/7073857431666704421