七、类(Class)
7、枚举
枚举在开发中也非常常见, 枚举也是一种特殊的类, 通常用于表示固定数量的常量值。
1. 枚举的定义
main(List<String> args) {
print(Colors.gray);
}
enum Colors {
black,
gray,
white
}
2.枚举的属性
枚举类型中有两个比较常见的属性:
- index: 用于表示每个枚举常量的索引, 从0开始.
- values: 包含每个枚举值的List.
main(List<String> args) {
print(Colors.black.index);
print(Colors.values);
var day = Colors.black;
switch(day){
case Colors.black:
break;
case Colors.gray:
break;
case Colors.white:
break;
default:
break;
}
}
enum Colors {
black,
gray,
white
}
枚举类型的注意事项:
- 注意一: 不能子类化、混合或实现枚举。
- 注意二: 不能显式实例化一个枚举
8、mixin
- 类在Dart中只能继承一个父类。
- mixin可理解为让类实现了继承多个父类的效果,但不是多继承,而是其目的是实现代码重用。
声明一个with类 - 跟声明普通类一样的方式。
- 区别在于,不声明构造函数,也不使用静态函数或变量。
- 纯粹的属性和函数。
怎么使用?子类声明时 - 后跟with+类名
- with后可跟多个类,用“,”分开 看个例子:
简单来讲 mixin 可以 “组合” 多个类,我们通过一个例子来理解。
class With1 {
String getName() => 'With1';//三个类都有该方法
String getAge()=> "With1 10" ;//该类独有
}
class With2 {
String getName() => 'With2';//三个类都有该方法
String getColor() => "With2 red";//该类独有
int getNum()=> 6;//该类和OtherClass都有
String getFruit() => "With2 banana";
}
class OtherClass {
String getName() => 'OtherClass';//三个类都有该方法
int getNum() => 3; //该类和With2都有
int getDesk() => 333;//该类独有
String getPhone()=>"OtherClass huawei";//该类和子类
String getFruit()=>"OtherClass apple";
}
class Child1 extends OtherClass with With1 ,With2 {
//重写父类
@override
String getPhone() {
return "Child1 iphone";
}
@override
String getFruit() {
return "Child1 oriange";
}
}
class Child2 extends OtherClass with With2, With1 {}
main(List<String> args) {
print("class Child1 extends OtherClass with With1 ,With2 {}");
Child1 c1 = Child1();
print(c1.getPhone());//Child1 iphone 重写了函数,调用时用的是自身的函数
print(c1.getFruit());//Child1 oriange 重写了函数,调用时用的是自身的函数
print(c1.getDesk());//333 调用的是OtherClass的函数 With1 With2中没有同名函数
print(c1.getNum());//6 调用的是With2中的函数
print(c1.getAge());//With1 10 调用的是With1中的函数
print(c1.getColor());//With2 red 调用的是With2中的函数
print(c1.getName());//With2 调用的是With2中的函数 With2在声明顺序中更靠后
print("-----------------------");
print("class Child2 extends OtherClass with With2, With1 {}");
Child2 c2 = Child2();
print(c2.getPhone());//OtherClass huawei 没有重写函数,调用时用的是OtherClass的函数
print(c2.getFruit());//With2 banana 没有重写函数,调用时用的是With2的函数,虽然OtherClass也有,但With2在声明顺序中更靠后
print(c2.getDesk());//333 调用的是OtherClass的函数 With1 With2中没有同名函数
print(c2.getNum());//6 调用的是With2中的函数
print(c2.getAge());//With1 10 调用的是With1中的函数
print(c2.getColor());//With2 red 调用的是With2中的函数
print(c2.getName());//With1 调用的是With1中的函数 With1在声明顺序中更靠后
}
总结
- A extends B with C,D{}
- A 继承了B 并拥有了C和D所有的属性和函数,可以用A的实例直接调用CD的属性方法。
- 如果B有函数fun1,A重写了这个函数 那么以后A的实例调用的fun1,都是A重写后的方法。
- 如果B有函数fun1,CD中也有函数fun1,A重写了这个函数 那么以后A的实例调用的fun1,都是A重写后的方法。
- 如果B有函数fun1,CD中也有函数fun1,A没有重写这个函数 那么以后A的实例调用的fun1,是声明方法时最后的那个类的函数,比如“A extends B with C,D”,那么就是D中的fun1,如果是“A extends B with D,C”,那么就是C中的fun1。也就是说优先级是从后向前的(前提是子类没有重写函数)。
9、extends、with、implements关系
- 三者可同时存在,但声明时有先后顺序
- 三者作用不同,extends是继承体系,单继承;mixins是做扩展功能,复用代码,可以理解为import了一个工具类;implements是接口实现
class A extends B with C implements D{}
10、as 和 is
- as ,A as B,将A转为B类型,然后可以使用B的属性方法了,前提是B是A的子类。
class A {
int getSum(int a,int b){
return a+b;
}
}
class B extends A{
int getSub(int a,int b){
return a-b;
}
}
main(List<String> args) {
A a = new B();
// a.getSum(1,3);// 没问题,调用A的方法
// a.getSub(1,3);// 报错,调用B的方法。
if (a is B){
a.getSub(1,3);//没问题, a 是 B的实例对象,因此判断 a is B 后,可以调用B的方法。
}
var c = a as B;// 将a 转为 B的实例,降维
c.getSub(1,3);// 这里a的确是B的实例对象,因此没问题,但是当a不是B的实例时,则会报错。
}
八、异步
1、Dart的异步模型
1.1、Dart的异步模型
1.1.1、耗时的操作
处理方式一: 多线程,比如Java、C++,我们普遍的做法是开启一个新的线程(Thread),在新的线程中完成这些异步的操作,再通过线程间通信的方式,将拿到的数据传递给主线程。
处理方式二: 单线程+事件循环,比如JavaScript、Dart都是基于单线程加事件循环来完成耗时操作的处理。不过单线程如何能进行耗时的操作呢?!
1.1.2、单线程的异步操作
Q: 单线程是如何来处理网络通信、IO操作它们返回的结果呢?
A: 事件循环(Event Loop)。
1.2、Dart事件循环
1.2.1、什么是事件循环
单线程模型中主要就是在维护着一个事件循环(Event Loop)。
- 将需要处理的一系列事件(包括点击事件、IO事件、网络事件)放在一个事件队列(Event Queue)中。
- 不断的从事件队列(Event Queue)中取出事件,并执行其对应需要执行的代码块,直到事件队列清空位置。
1.2.2. 事件循环代码模拟
这里我们来看一段伪代码,理解点击事件和网络请求的事件是如何被执行的:
1. 一个按钮RaisedButton,当发生点击时执行onPressed函数。
2. onPressed函数中,我们发送了一个网络请求,请求成功后会执行then中的回调函数。
RaisedButton(
child: Text('Click me'),
onPressed: () {
final myFuture = http.get('https://example.com');
myFuture.then((response) {
if (response.statusCode == 200) {
print('Success!');
}
});
},
)
这些代码是如何放在事件循环中执行呢?
1. 当用户发生点击的时候,onPressed回调函数被放入事件循环中执行,执行的过程中发送了一个网络请求。
2. 网络请求发出去后,该事件循环不会被阻塞,而是发现要执行的onPressed函数已经结束,会将它丢弃掉。
3. 网络请求成功后,会执行then中传入的回调函数,这也是一个事件,该事件被放入到事件循环中执行,执行完毕后,事件循环将其丢弃。
2、Dart的异步操作
Dart中的异步操作主要使用Future以及async、await。
2.1、认识Future
2.1.1、同步的网络请求
我们先来看一个例子吧:
1. 在这个例子中,我使用getNetworkData来模拟了一个网络请求;
2. 该网络请求需要3秒钟的时间,之后返回数据;
import "dart:io";
main(List<String> args) {
print("main function start");
print(getNetworkData());
print("main function end");
}
String getNetworkData() {
sleep(Duration(seconds: 3));
return "network data";
}
3. getNetworkData会阻塞main函数的执行
main function start
// 等待3秒
network data
main function end
显然,上面的代码不是我们想要的执行效果,因为网络请求阻塞了main函数,那么意味着其后所有的代码都无法正常的继续执行。
2.1.2. 异步的网络请求
我们来对我们上面的代码进行改进,代码如下:
和刚才的代码唯一的区别在于我使用了Future对象来将耗时的操作放在了其中传入的函数中;
import "dart:io";
main(List<String> args) {
print("main function start");
print(getNetworkData());
print("main function end");
}
Future<String> getNetworkData() {
return Future<String>(() {
sleep(Duration(seconds: 3));
return "network data";
});
}
我们来看一下代码的运行结果:
- 这一次的代码顺序执行,没有出现任何的阻塞现象;
- 和之前直接打印结果不同,这次我们打印了一个Future实例;
- 结论:我们将一个耗时的操作隔离了起来,这个操作不会再影响我们的主线程执行了。
- 问题:我们如何去拿到最终的结果呢?
main function start
Instance of 'Future<String>'
main function end
获取Future得到的结果
有了Future之后,如何去获取请求到的结果:通过.then的回调:
main(List<String> args) {
print("main function start");
// 使用变量接收getNetworkData返回的future
var future = getNetworkData();
// 当future实例有返回结果时,会自动回调then中传入的函数
// 该函数会被放入到事件循环中,被执行
future.then((value) {
print(value);
});
print(future);
print("main function end");
}
上面代码的执行结果:
main function start
Instance of 'Future<String>'
main function end
// 3s后执行下面的代码
network data
执行中出现异常
如果调用过程中出现了异常,拿不到结果,如何获取到异常的信息呢?
import "dart:io";
main(List<String> args) {
print("main function start");
var future = getNetworkData();
future.then((value) {
print(value);
}).catchError((error) { // 捕获出现异常时的情况
print(error);
});
print(future);
print("main function end");
}
Future<String> getNetworkData() {
return Future<String>(() {
sleep(Duration(seconds: 3));
// 不再返回结果,而是出现异常
// return "network data";
throw Exception("网络请求出现错误");
});
}
上面代码的执行结果:
main function start
Instance of 'Future<String>'
main function end
// 3s后没有拿到结果,但是我们捕获到了异常
Exception: 网络请求出现错误