目录
最近在学Flutter,也顺便用优快云做一些笔记,后面好翻看~
遇到的一些坑:
https://www.cnblogs.com/lovewhatIlove/p/16323828.html
解决 Android Gradle plugin requires Java 11 to run. You are currently using Java 1.8_刘楼主的博客-优快云博客
路径设置到安装路径即可,如:org.gradle.java.home=D:\\jdk11
开发环境安装
VS CODE 开发Dart
1、先把Dart安装了,然后再安装Code runner(用于运行dart文件)
Dart语言
纯对象编程语言,所有基本类型都是对象。
对所有数据都是统一处理(如集合类能够操作任意类型的数据)
面向对象的核心是关注对象的行为而非内部实现:
1、没有final方法,允许重写除内置操作符外的所有方法。
2、基于接口而非类。
类型可选:
1、类型在语法层面上可选
2、类型对运行时的语义没有影像。
代码中类型不一致或遗漏,Dart编译器会告警但不会报错。
基于语法
入口函数
void main(){
}
注释
单行:// 或 ///
多行:/* */
命名规则
变量与常量
Dart是脚本类语言,可以不预先定义变量类型,系统自动会类型倒推。Dart有类型校验。
使用var定义实例变量,定义时没赋值,默认变量值为null。
void main(){
var apple='apple';
String a="apple";
print(apple);
}
Dart是强类型语言,第一次赋值为字符串类型,后面就不能更改为其他类型(例如var s='';s=123;会报错),这点与javascript有区别。当然如果要改变类型,可以用dynamic。
定义常量用final或const。
final nums=300;
const number=200;
final和const:
初始化后都无法更改
const编译时检查值,final运行时才检查,所以不能给const定义的常量赋值为不确定的值(2、4行)
final是惰性初始化,在运行时第一次使用前才初始化。
const time='2020-05-03';
const time=DataTime.now();//这句话在编译器中会报错
final time='2020-05-03';
final time=DataTime.now();//这句不会报错
内置类型
基本内置类型
数值类型、布尔类型、字符串、列表、键值对、动态类型与Object、符号字符、符号
数值类型
整数类型int、浮点数类型double。
int类型的不能赋值为double,double可以赋值为int的,会自动转换。
int、double都继承自num。
Dart中int大小不限,但是超过一定的范围,操作int时不能直接获得底层硬件提供的优势。
Dart中虽然一切皆对象,但不能定义int、double的子类型或用另一个类型实现int。(牺牲继承来实现高性能)。
两个double 的底层位模式所表示的浮点数相同,则他们相关,除了下面两种场景:
1、浮点数-0.0 与0.0相等但不相同
2、NaN(非数值)与自身不相等但相同。
布尔类型
bool有两个值:true/false。
注:
1、程序中用表示布尔类型
2、布尔值是编译时常量
3、debug下,课通过断言函数assert()判断布尔值。如果不为true,则引发异常并终止程序。
Dart中,bool不像其他语言可以强制转换表达式的值为bool。
下面案例会报错
void main(){
if(123){//这句代码会报错
print('获取到了返回值');
}
}
Dart本身不支持内置强制类型转换。
判断函数是否有返回值,可以用:func() != null
字符串类型
创建方式:
1、单双引号创建
2、三个单双引号创建多行字符串
3、r创建原始字符串 r"ferfe\n"
操作方式:
1、运算符操作:+ * == []
2、插值表达式: ${name}
3、常用字符串属性:length、isEmpty、isNotEmpty
4、常用方法:contains() subString() replaceAll() split() trimRight() trimLeft() trim() toUpperCase() toLowerCase() lastIndexOf() indexOf() endsWith() startsWith()
注:字符串中单引号嵌套前引号,双引号嵌套双引号,需加入反斜杠\进行转义;或单引号嵌套双引号,双引号嵌套单引号的方式
字符串拼接也不一定要加号,但推荐用+拼接,不用+如下例子
void main(){
var str='hello ' 'world';//单引号拼接
var str1="hello " "world";//双引号拼接
var str2="hello " 'world';//单双引号拼
}
插值操作:
void main(){
var name = 'Li YuanJing';
print("My name is ${name}"); //可以不用加括号
}
列表(集合)类型
创建列表
var list = [1, 2, 3, "123"];
//通过new创建
var list1 = new List(); //3版本已废弃
var list1 = <int>[] //新版,定义式指定元素类型
//创建一个不可变的List
const list2 = const [1, 2, 3];
可以通过索引访问或改变可变列表的内容。
列表常用方法
add length remove insert indexOf sublist forEach shuffle
var list1=["apple","banana","cherry"];
print(list1.length); //获取长度
list1.add("bayberry"); //末尾添加元素
list1.remove("apple"); //移除元素,有多个相同的则移除第一个,删除成功返回true
list.insert(1, "dates"); //在1索引插入字符串
list.indexOf("test"); //获取元素所在位置
list.sublist(2); //去除前后两个元素后的新列表
list.forEach(print) //遍历并输出列表
list.shuffle() //打乱顺序
var ls = List.filled(length, fill) //创建一个固定长度的集合,此时没法增加数据
var ls = List<String>.filled(length, fill) //指定类型
通过中括号创建的list,使用length可以改变长度,达到删除的功能
var ls = [1,2,3,4];
ls.length = 2;
print(ls);
[1, 2]
ls.join(',')
Set类型
会去重
var s = new Set();
s.add("123"); //新增元素
s.toList(); //转换成list
s.addAll(list); //插入多个元素
键值对类型
键和值都可以是任意类型的对象,但是key只能出现一次
创建方式
Map map = {"name": "a", "age": 16};
Map map1 = new Map();
map['name'] = 'liyuanjing';
map['age'] = 27;
设置键值对时并没有定义详细的类型,因为Map类型有两个泛型类参数,系统理解为Map<dynamic,dynamic>
操作
map.remove('name');//删除键为name的数据
map.clear(); 清空键值对
foreach、map、where、any、every
var ls = [1,2,3];
ls.forEach((value){print(value)});
一行代码也可以用箭头函数:
ls.forEach((value)=>print(value));
var ls1= ls.map((value){return value*2}); //对每项 * 2
var ls2 = ls.where((value){return value>5}); //过滤,返回>5的
var f = ls.any((value){return value>5}); //有一个元素满足条件就返回true
var f = ls.every((value){return value>5}); //每一个都满足条件才返回true
var dic = {"name": "123", "age":12}
dic.forEach((key,value){print("$key --- $value")})
动态类型与Object
Dart中对象的父类都是Object
如前面的Map映射中,没有明确键值对类型时,编译器会自动根据值明确类型。
var name1 = 'liyuanjing';
Object name2 = 'liyuanjing';
dynamic name3 = 'liyuanjing';
这三种操作都没问题,但不建议这么做,还是要尽量确定类型,提高程序的安全性,也能加快运行速度。
所有类型继承Object,如果调用不存在的方案,会抛出NoSuchMethodError异常。
所以赋值操作前,应先用is或as判断
dynamic map = {'name': 'liyuanjing', 'age': '27'};
if (map is Map<String, String>) {
print('类型与你想的一致');
}
var map2 = map as Map<String, String>;
is判断类型时使用,as转换类型使用。 is!与is功能相反。
符号字符
符号字符是Dart提供的采用UTF-32编码的字符串,可通过这些编码直接转换成表情包与特定的文字。
前面的字符串使用UTF-16编码。
void main(){
var clapping = '\u{1f44f}';
print(clapping);
print(clapping.codeUnits);
print(clapping.runes.toList());
Runes input = new Runes(
'\u2665 \u{1f605} \u{1f60e} \u{1f47b} \u{1f596} \u{1f44d}');
print(new String.fromCharCodes(input));
}
符号
符号用来表示程序中声明的名称,使用#开头,后面跟一个或多个用点分隔的符号或运算符。
#MyClass #i #[] #com.example.test
运算符
常用运算符
1、赋值运算符:基本赋值运算及所有复合运算符
2、成员选择是的点操作符,级联操作中的双点运算符
3、不相等运算符,相等运算符
4、逻辑非运算符
5、后置运算符,自增自减
6、布尔逻辑运算符
算术运算符
int a = 13;
int b = 5;
print(a+b);//加 18
print(a-b);//减 8
print(a*b);//乘 65
print(a/b);//除 2.6
print(a%b);//取余 3
print(a~/b);//取整 2
a--; //a = a-1
++a; //前缀自增,先自增操作
关系运算符
int a = 5;
int b = 3;
print(a==b);//false
print(a!=b);//true
print(a>b);//true
print(a<b);//false
print(a>=b);//true
print(a<=b);//false
//关系运算符主要用于条件判断中
逻辑运算符
!(取反) &&(并且) ||(或者)
bool flag1 = true;
bool flag2 = false;
//&& 只有全部为true则输出true,否则为false
print(flag1 && flag2);//false
//|| 全为false则输出false,否则为true
print(flag1 || flag2);
//! 取反 true变成false false变成true
print(!flag1);//false
赋值运算符
= 、??=
// ??= 表示如果a为空的话,则把5赋值给a
int a = 4;
a ??= 5;
print(a);//因为a已经有值,所以输出4
int c;
c ??= 6;
print(c);//6
- 复合赋值运算符 += 、-=、 *=、 /= 、%= 、~/=
var a = 10;
//假设要给a+10 我们可写成
a = a + 10;
print(a);//20
//也可写成
a+=10;
print(a);/20
//复合赋值运算是由算术运算符和赋值运算符=结合而成,剩下的就不一一举例啦
条件表达式
- if else switch case
//判断成绩 大于60及格 大于70良好 大于90优秀
int a = 75;
if(a > 90){
print("优秀");
}else if(a > 70){
print("良好");
} else if (a > 60){
print("及格");
} else {
print("不及格");
}
//最终输出 良好
switch判断固定值效率比if else高
var sex = "男";
switch(sex){
case "男":
print("是男的");
break;
case "女":
print("是女的");
break;
default:
print("啥也不是");
break;
}
//最终输出 是男的
类型转换
- Number与String类型之间的转换
Number类型转成String类型 toString()
var num = 456;
var str = num.toString();
print(str is String);//输出 true
String str = "123";
var num = int.parse(str);
print(num is int);//输出true
判断是否空: var is null
数值类型判断是否空: num.isNaN
三目运算符
var number1;
var number2 = 2;
var test=number1 ?? number2;
上面number1如果为空则把number2赋值给test,number1不为空会报错。
int a = 200;
var b = a > 10 ? 1 : 2;
print(b);
自定义类操作符(重载操作符)
class Point {
var x, y;
Point(this.x, this.y);
operator +(p) => new Point(x + p.x, y + p.y);
@override
String toString() {
// TODO: implement toString
return "x=" + x.toString() + ";y=" + y.toString();
}
}
void main(){
Point s_one = new Point(10, 10);
Point s_two = new Point(20, 20);
print(s_one + s_two);
}
得到:x=30;y=30
级联操作符
通过..调用
void main(){
String fruits = (new StringBuffer()
..write('apple')..write('banana')..write('apricot'))
.toString();
print(fruits);
}
applebananaapricot
get与set
Dart中,如果属性是公开的,可以直接通过[类.属性]访问 或[类.属性=值]设置值,这样的方法默认就是调用了get、set。
Dart中只要属性不是私有的,就可以直接用get、set方法,不需要手动定义、重写。但用_将属性设置为私有,如var _x,默认的get、set就失效了,需要手动设置方法来提供对应的get、set。
Flutter中禁止使用反射机制,因此没有和java一样的getxxx、setxxx等通用方法名。
Dart中的get和set方法名可以任意。
Dart类中的set、get方法中,不要调用自身类的方法,因为Dart有层级树的概念,递归调用会导致Stack Overflow 堆栈溢出异常。
异常捕获
throw
Dart异常都是没有被终止的,可以继续传递。
Dart中可以抛出任何类型的异常;
throw Exception("异常");
Dart中任意对象都可以作为异常被抛出,并不要求抛出的对象是个特殊异常类的实例或子类。且throw是个表达式。
try-catch
try{
...
}on String catch(e){
...
}catch(e){
...
}finally{
...
}
catch为捕获的异常定义处理逻辑;finally用于定义异常捕获处理结束后需要做什么,无论是否发生,finally都会运行。
上面例子,预料可能会抛出一个字符串类型的异常,如果不是字符串类型的异常,则第二个catch会捕获任意可能出现的异常。
Dart还有一种异常处理rethrow。捕获的异常经检查之后,如果发现本地无需处理异常,并且异常应该在调用链中向上传播,此时可以用rethrow,它在catch子句中将捕获的异常重新抛出。
循环语句
Dart的三种循环:for、while、do-while
for循环
for-in
for (int i = 0; i < 10; i++)
print(i);
for (var i = 0; i < 10; i++)
print(i);
Dart中实现了Iterable接口的类,都能用for-in
经典for循环
for(int i=1;i<=100;i++)
print(i);
for(var i=1;i<=100;i++)
print(i);
while循环
经典while
int i=1;
while(i<=100){
print(i);
i++;
}
do-while
int i = 1;
do {
print(i);
i++;
} while (i <= 100);
continue、break同python使用方法一致
switch语句
每个case子句的值都是编译时常量,他们所有值的类型也一致。
void main(){
var grade='周杰伦';
switch(grade){
case '周杰伦':
print("歌手");
break;
case '屠呦呦':
print("科学家");
break;
default:
break;
}
}
函数
自定义方法
格式:
[返回类型] 方法名(参数列表){ //可以没有返回值类型
方法体……
return 返回值;
}
一切皆对象,函数也是对象,函数的对象类型为Function。
Dart语言与flutter项目的顶层函数都是main(),其是程序运行的入口,返回值类型为void,且具有列表的可选参数。
//Dart
void main(){
}
//Flutter
void main()=>runApp(MyApp());
可选参数
可以传入也可以不传入
void setUser({String name, int age}){
//...
}
Dart中通过{}声明可选参数
必选参数
两种方式,直接定义函数参数或通过修饰符@required修饰
import 'package:meta/meta.dart'; //必须引入该包,才能用修饰符@required
void setUser(String name, int age){
}
void setUser({@required String name, int age}){
}
可选位置参数
使用[]定义可选位置参数
void setUser({String name, int age, [String company]}){
if(company!=null){
print(company);
}
}
默认参数
void setUser({String name="", int age=18}){}
命名参数
String test(String name,{int age, String sex}){}
调用
test("abc", age:17)
这里会有个问题,会报错“由于参数name是String类型,值不能为null,但隐式的默认值是null。”
参考:Dart报错处理—The parameter ‘xxx‘ can‘t have a value of ‘null‘ because of its type ‘XXX‘, but...-优快云博客
原因:Dart启用了空安全,不可空的参数因子或键就不能为空。Dart在2.12版本之后开始支持空安全(Null Safety):空安全是一种对可能为空的变量在编译期就进行校验的一种审查机制,有了空安全,原本处于运行时的空值引用错误将变为编辑时的分析错误。这种检查机制对所有变量的默认值也进行了改变,以前默认为null,而使用空安全后,代码中的类型将默认是非空的,意味着除非你声明它们可空,否则它们的值都不能为空并且会报错。
解决:
方法一:
创建变量时,如果想要一个变量可为null,需要显式声明为可空的类型,具体就是使用“?”进行声明。比如String?、int?等,表示变量是一个可空类型,可以赋值为空。
void func1({String? name, int? age}) {
print('$name, $age');
}
调用时不传入,则会为null类型
方法二:
另一种解决方案是给参数设置默认值。如果参数有默认值,在调用函数时如果不对参数传值,则使用默认值。
void func2({String name="", int age=0}) {
print('$name, $age');
}
方法三:
使用required标记。使用required来标记一个可选命名参数,这样被标记的参数在函数调用时就必须要进行传值,使得参数不为空,在有参数未传值的情况下则会报错。
void func3({required String name, required int age}) {
print('$name, $age');
}
函数作为参数传递
void printName(String name){
print(name);
}
void main(){
var fruits = ['apple', 'banana', 'apricot'];
fruits.forEach(printName);
}
函数作为变量
void main(){
var night = (good) {
print(good + 'night');
};
night('good');
print(night.runtimeType);
}
级联
Dart中通过括号后的参数列表调用函数,当没有用{}时,直接通过几个参数调用;当使用{}时,需要给出参数的名称,以键值对的形式调用,如setUser(name:"hhh")
级联是相对于对象进行的,但Dart一切皆对象,函数也是对象,因此也能用级联操作。
void main(){
print('Hello'.length);
print('Hello'..length);
}
5
Hello
可以看出.返回的是函数的返回值,而级联操作符就像普通的方法调用,返回的是当前对象。
箭头函数
ls.forEach((value)=>print(value));
ls.forEach((value)=>{
print(value) //没有分号,只能一行语句
});
ls. map((value)=>value>2?value*2:value); //大于>2则*2
匿名方法
var printNum=(){
print(123);
}
printNum(); //调用
//传参
var printNum=(int n){
print(n + 123);
}
自执行方法
((){
print(123);
})();
//传参
((int n){
print(n);
})(12);
递归方法
var num = 1;
fn(n){
sum *= n;
if (n == 1){
return ;
}
fn(n - 1);
}
fn(5)
print(num);
闭包
全局变量特点:常驻内存,全局变量污染全局
局部变量特点:不常驻内存会被垃圾回收机制回收、不污染全局
闭包:可实现常驻内存又不污染全局。函数嵌套函数,内部函数会调用外部函数的变量或参数,变量或参数不会被系统回收。
闭包写法:函数嵌套函数,并return内层的函数,就形成闭包。
main() {
var b = fn();
b();
b();
b();
}
fn(){
var a = 123; //不会污染全局 常驻内存
return (){
a++;
print(a);
};
}
输出
124
125
126
类
静态成员
1、使用static关键字实现类级别的变量和函数
2、静态方法不能访问非静态成员,非静态方法可以访问静态成员(直接通过变量名/方法名访问)和非静态成员(建议通过this.方法名/变量名访问)
静态成员不能通过实例化对象去访问,而应该通过类名去访问:类型.fun/var
对象操作符
? 条件运算法
class Person{}
void main(){
Person p;
p?.print();
}
如果为p为null则不会调用,也不会报错;当p不为null时才会调用print()
is 类型判断
print(p is Person) //true
print(p is Object) //true
as 类型转换
var p1;
p1="";
p1 = new Person("a",15);
p1.print() ; //老版本这里会报错,不知道p1是什么类型;新版本就不会了
(p1 as Person).print(); //可以强制转换为Person类型再调用。
.. 级联操作
Person p1 = new Person("ab", 18);
p1..name="ac"
..age=30
..print();
因为这里p1..name 之后返回的还是p1对象
class Person{
String name="null";
int age=0;
Person(this.name, this.age); //构造函数
void print1(){
print("$name --- $age");
}
}
void main(List<String> args) {
Person p = new Person("123",15);
p.print1();
p..name="1234"..age=16..print1();
}
继承
面向对象三大特性:封装、继承、多态
继承:
1、使用extends
2、子类会继承父类里可见的属性和方法 但不会继承构造函数
3、子类能复写父类的方法 getter和setter
Flutter中的继承只能是单继承,继承一个类后,可以通过@override重写父类方法,也可以用super调用超类中的方法。构造方法不能被继承。Dart中没有公有与私有的访问修饰符,所有子类可以访问超类中的所有方法和属性。
class Person{
String name="null";
num age=0;
Person(this.name, this.age); //构造函数
Person.xxx(this.name, this.age); //命名构造函数
void print1(){
print("$name --- $age");
}
}
class Test extends Person{
//Test(String name,num age): super(name, age); //构造函数没内容这样写就可以了
Test(String name,num age): super(name, age){ //super是先调用父类的构造函数进行初始化
}
}
void main(List<String> args) {
Test t = new Test("name", 17);
t.print1();
}
重写父类方法建议写上@override;子类中调用父类成员,使用super。
抽象类
Dart中抽象类主要用于定义标准,子类可以继承抽象类,也可以实现抽象类接口
1、抽象类通过abstract关键字定义
2、Dart中的方法不能用abstract声明,Dart中没有方法体的方法称为抽象方法。
3、子类继承抽象类必须实现里面的抽象方法
4、如果把抽象类当做接口实现的话必须得实现抽象类里定义的所有属性和方法。
5、抽象类不能被实例化,继承它的子类才可以
Dart中抽象类不能实例化,否则会报abstractClassInstantiation错误,Dart解释器也会发出警告。
extends抽象类和implements的区别:
1、如果要复用抽象类里面的方法,且要用抽象方法约束子类的话就用extends继承抽象类
2、如果只是把抽象类当做标准的话就用implements实现抽象类。
定义一个Animal类并约束它的子类必须包含eat方法
abstract class Animal{
eat(); //抽象方法,子类必须重写
printInfo(){ //抽象类也可以定义非抽象方法,子类不一定要重写该方法
print("非抽象方法")
}
}
class Dog extends Animal{
@override
eat() { //子类必须实现父类的方法
// TODO: implement eat
print("eat");
}
}
void main(List<String> args) {
Dog d = new Dog();
d.eat();
}
多态
允许将子类类型的指针赋值给父类类型的指针,同一个函数调用会有不同的执行结果。
子类的实例赋值给父类的引用。
多态就是父类定义一个方法不去实现,让继承他的子类去实现,每个子类有不同的表现。
abstract class Animal{
eat(); //抽象方法,子类必须重写
}
class Dog extends Animal{
@override
eat() { //子类必须实现父类的方法
// TODO: implement eat
print("Dog");
}
run(){
print("run");
}
}
class Cat extends Animal{
@override
eat() { //子类必须实现父类的方法
// TODO: implement eat
print("Cat");
}
run(){
print("run");
}
}
void main(List<String> args) {
Animal d = new Dog(); //把子类的实例赋值给父类的引用,只能调用父类的标准,然后在子类中实现这个标准方法
d.eat(); //不能调用run,因为Animal并没有定义run
Animal c = new Cat();
c.eat();
}
输出
Dog
Cat
接口
Dart的接口没有interface关键字定义接口,而是普通类或抽象类都可以作为接口实现,使用implements关键字。
dart接口比较奇怪,如果实现的类是普通类,会将普通类和抽象中的属性的方法全部覆写一遍。
而因为抽象类可以定义抽象方法,普通类不可以,所以一般如果要实现像java接口那样的方式,一般会用抽象类。
建议用抽象类定义接口。
定义一个DB库 支持mysql、mssql、mongodb
abstract class Db{ //当做接口 接口:约定、规范
late String uri;
add(String data);
save();
delete();
}
class MySql implements Db{
@override
String uri; //现在变量必须初始化,要么通过下面的构造函数,要么改成late String uri
MySql(this.uri);
@override
add(String data) {
// TODO: implement add
print("Mysql add");
}
@override
delete() {
// TODO: implement delete
print("Mysql delete");
}
@override
save() {
// TODO: implement save
print("Mysql save");
}
}
void main(List<String> args) {
MySql mysql = new MySql("test");
mysql.add("123");
}
Dart中每个类都隐含地定义一个接口,此接口描述了类的实例具有哪些方法。Dart虽然有接口,但没有接口声明,Dart始终可以定义一个抽象类来描述所需的接口。
接口的定义与操作
abstract class Point{
get x;
get y;
}
class xyzPoint implements Point{
...
}
xyzPoint并不是Point的子类,它没有继承Point的任何方法与成员。implements的作用是在接口中建立预期的关联,而不是共享实现。
Dart并不关心对象如何实现,而只在意它支持哪些接口。is也用来判断类与类的接口。
如果类中的方法也是接口中的方法,那么实现这个接口的子类时都必须重写其方法,而且要加上@override。
实现多接口
一个类实现多个接口,则这个类要实现所有接口的属性和方法
abstract class A{
late String name;
printA();
}
abstract class B{
printB();
}
class C implements A,B{
@override
late String name;
@override
printA() {
return null;
}
@override
printB() {
return null;
}
}
mixin
mixin是Dart独有的混入语法特性,是为了解决多继承问题。
mixins使用条件随着Dart版本一直在变,这里说的是Dart2.x中使用mixins的条件:
1、作为mixins的类只能继承自Object,不能继承其他类(如下面案例的A、B类)
2、作为mixins的类不能有构造函数(如下面案例的A、B类)
3、一个类可以mixins多个mixins类
4、mixins绝不是继承也不是接口,而是一种全新的特性
class A{
void printA(){
print("A");
}
}
class B{
void printB(){
print("B");
}
}
class C with A, B{ //类似继承,此时它会拥有A和B的所有属性和方法
//这里mixins顺序是先A后B,此时如果A、B有同名方法,是调用B的
}
void main(List<String> args) {
C c = new C();
c.printA();
c.printB();
}
也可以继承和mixins一起用
class Person{
String name;
num age;
Person(this.name, this.age);
void printInfo(){
print("${this.name} --- ${this.age}");
}
}
class C extends Person with A, B{
C(String name, num age):super(name, age);
}
可以发现接口、继承、mixin的优先级应该是:mixin、extends、implements
mixins的实例类型是 其超类的子类型,此时A、B、Person都为C的超类
print(c is C);
print(c is A);
print(c is B);
print(c is Person);
//结果均为true
继承抽象类,必须重写其方法,而对于mixin混入的类,不必强制重写其方法。
1、mixin可以实现类似多继承的功能,但实际上mixin和多继承不一样,多继承中相同的函数运行并不会存在“父子”关系
2、mixin可以抽象和复用一系列特征
3、mixin实际上实现了一条继承链
泛型
解决类、接口、方法的复用性,以及对不特定数据类型的支持(类型校验)
// 支持传入 string类型且返回string类型
String getData(String value){
return value;
}
// 不指定类型,但放弃了类型检查,不知道返回的会是个什么类型,我们希望传什么类型返回什么类型
getData(value){
return xxx;
}
//使用泛型 第一个T表示返回值类型,第二个表示指定什么类型,传入这个才会类型校验,第三个表示传入参数的类型
T getData<T>(T value){
return value;
}
调用
print(getData("xxx"));
print(getData(12));
前两个都不会做类型校验,下面这个会做类型校验,相当于把String赋给了T
getData<String>("xxx"); //正常
getData<String>(123); //报错
也可以只对传入参数校验,而不校验返回参数
getData<T>(T value){
return value;
}
泛型类
List list1 = new List<String>.filled(2, "")
上面就是创建固定类型指定长度的列表,其中List就是个泛型类
var ls = <int>[];
List ls = <String>[];
将普通类改成泛型类
class Mylist<T>{
List list = <T>[];
void add(T value){
this.list.add(value);
}
List getlist(){
return list;
}
}
void main(List<String> args) {
Mylist l1 = new Mylist(); //此时不指定类型,可以添加任何类型数据
l1.add("value");
l1.add(12);
print(l1.getlist());
Mylist l2 = new Mylist<String>(); //实例化时指定类型,此时只能传入指定类型数据
l2.add("value");
// l2.add(12); //运行时报错
print(l2.getlist());
}
泛型接口
接口可以用抽象类或普通类定义,优先用抽象类
abstract class Cache<T>{
getByKey(String key); //抽象方法
void setByKey(String key, T value);
}
class FileCache<T> implements Cache<T>{
@override
getByKey(String key) {
return null;
}
@override
void setByKey(String key, T value) {
print("文件缓存 key=$key value=$value");
}
}
class MemoryCache<T> implements Cache<T>{
@override
getByKey(String key) {
return null;
}
@override
void setByKey(String key, T value) {
print("内存缓存 key=$key value=$value");
}
}
void main(List<String> args) {
MemoryCache m = new MemoryCache<String>();
m.setByKey("index", "value");
// m.setByKey("index1", 12); //报错
}
库
库通过import引入
library指令可以创建一个库,每个dart文件都是一个库。
Dart中主要的三种库
1、自定义的库: import 'lib/xxx.dart'
2、系统内置库
import 'dart:math';
import 'dart:io';
import 'dart:convert';
3、Pub包管理系统中的库
https://pub.dev/packages
https://pub.flutter-io.cn/packages
https://pub.dartlang.org/flutter/
- 需要在自己项目根目录新建一个pubspec.yaml
- 在pubspec.yaml文件配置名称、依赖、描述等信息
- 运行pub get获取包下载到本地
- 项目中引入库import 'package:http/http.dart' as http;
import 'dart:math';
void main(List<String> args) {
print(min(12, 52));
}
import 'dart:io' as io; // http请求库
import "dart:convert"; //编码库
void main(List<String> args) async { //定义成异步方法
var res = await getDataFromApi();
print(res);
}
//api接口:http://news-at.zhihu.com/api/3/stories/latest
getDataFromApi() async{ //定义成异步方法
// 1、创建HttpClient对象
var httpClient = new io.HttpClient();
// 2、创建Uri对象
var uri = new Uri.http("news-at.zhihu.com", "/api/3/stories/latest");
//3、发起请求,等待请求
var request = await httpClient.getUrl(uri);
//4、关闭请求,等待响应
var response = await request.close();
//5、解析响应的内容
return await response.transform(utf8.decoder).join();
}
引入第三方模块(pub)
例如我们使用下面这个库
1、从上面三个网站找到要用的库
2、创建一个pubspec.yaml文件,内容如下
3、运行pub get获取远程库(这里报错了)
然后重新运行flutter pub get ,还是报下面操作
pubspec.yaml has no lower-bound SDK constraint.
You should edit pubspec.yaml to contain an SDK constraint:
environment:
sdk: '>=2.10.0 <3.0.0'
See https://dart.dev/go/sdk-constraint
按提示所说把environment:
sdk: '>=2.10.0 <3.0.0'加到pubspec.yaml文件中重新运行命令。
4、查文档引入使用
import 'dart:convert' as convert;
import 'package:http/http.dart' as http; //第三方库通过package导入
void main(List<String> arguments) async {
// This example uses the Google Books API to search for books about http.
// https://developers.google.cn/books/docs/overview
var url = "http://www.phonegap100.com/appapi.php?a=getPortalList&catid=20&page=1";
// Await the http get response, then decode the json-formatted response.
var response = await http.get(url);
if (response.statusCode == 200) {
var jsonResponse = convert.jsonDecode(response.body);
print(jsonResponse);
} else {
print("Request failed with status: ${response.statusCode}.");
}
}
库的重命名:import 'xxx' as name1; name1.test();
部分引入
只导入需要的部分
import 'xxx.dart' show foo; //此时只会引入foo方法
隐藏不需要的部分
import 'xxx.dart' hide foo; //此时除了foo方法其他都会引入
延迟加载
也称懒加载,可以在需要的时候再进行加载。
好处:减少APP的启动时间
使用deferred as来指定
import 'package:deferred/hello.dart' deferred as hello;
需要使用的时候,用loadLibrary()方法加载:
greet() async{
await hello.loadLibrary();
hello.printGreeting();
}
拆分库
例如io库的源码就有很多part,库过于庞大,可以拆分到多个文件中
library game;
part 'test1.dart';
part 'test2.dart';
每个part都指向一个part所对应的URI,这些uri与导入语句遵循同样的规则,而且所有的part都共享一个作用于,即导入它们的库的内部命名空间,且包含所有导入。
通过part拆分导入,那么part导入的单个模块的声明方式如下:
//test1.dart文件开头
part of game;
async和await
只有async方法才能使用await关键字调用方法
如果要用别的async方法必须使用await关键字
async是让方法变成异步
await是等待异步方法执行完成
最简单的异步方法:
void main(List<String> args) async {
var res = await test();
print(res);
}
test() async{
return 'hello';
}
异步编程
Dart与JavaScript都是单线程的,因此代码中出现同步代码,就会阻塞线程。而Flutter项目大多是并发的,因此程序中需要有大量异步操作。Dart中,异步操作是用Future对象来执行的,并且使用时需要搭配async以及await关键字。
Future
Future本身也是一个泛型对象,程序中大多不会单独用Future,而是用Future<T>,运算返回的结果对象就是T。如果返回结果不可用或无返回结果,Future类型会是Future<T>。
Dart中Future由dart:async库提供,如果返回Future函数,将会发生以下两件事情。
1、这个函数加入待完成的队列并且返回一个未完成的Future对象。
2、当这个操作结束时,Future会返回一个值或返回错误。
Future往往搭配then()方法使用,then接收一个onValue闭包作为参数,该闭包在Future成功完成时被调用
Future<File> copy(File, File);
s_file.copy(file)
.then((f){...})
.catchError((error)=>{...})
.whenComplete()=>{...};
上面定义一个文件复制的Future函数,复制成功后,copy()方法会返回一个真正的File对象,这个对象会传入then()方法的闭包中,其中参数f就是返回的File对象,如果复制失败会运行catchError方法,无论成功失败,最后都会运行whenComplete()进行回调。这点类似前端的Promise。
async和await
如上述Future开始工作后,有成功、异常等处理,任务多。为减轻异步操作的工作量,Dart为异步函数提供了async关键字,函数体可以用async操作
async用法
Future<int> printNum() async=>22;
这里如果调用printNum,函数不会立刻运行,而是安排在未来某个时间段运行,async真正价值在于与await搭配使用。
碰到耗时的任务,可以通过async与await将任务放到延迟的运算队列,先处理不需要延迟的运算,在处理耗时的运算,await必须搭配async使用,不然会报错。
假设不使用async与await
task1("task1").then((task1Result){
task2("task2").then((task2Result){
task3().then((task3Result){
...
});
});
});
任务task123都是异步的,但必须依次执行,这样会形成“回调地狱”,代码不美观不易理解。
使用async和await修改
task() async{
try{
String task1Result = await task1("task1");
String task2Result = await task2(task1Result);
String task3Result = await task3(task2Result);
...
}catch(e){
print(e);
}
}
Dart2.13之后的一些新特性
Null safety
即空安全,可以帮助开发者避免一些日常开发中很难发现的错误,并且额外的好处是可以改善性能。
Flutter2.2.0(2021-5-19发布)之后的版本都要求使用null safety。
?可空类型;!类型断言
void main(List<String> args) {
int a = 123; //非空的int类型
a = null; //会报错 不能把null赋值给int类型
print(a);
String username="123";
username=null; //同样报错
}
如何解决上面这个问题,需要用到?表示可空类型
String? a = "123"; //String? 表示a是一个可空类型
a = null;
方法返回一个可空类型
String getData(url){
if (url != null){
return "aaa";
}
return null;
}
类型断言
String? str = "test";
str = null;
// 这里不用!的话,前面将str复制为null,就会报错没有length属性
print(str!.length); //类型断言:如果str不为null,就能正常调用str的length;为null,抛出异常
late关键字
用于延迟初始化
class Person{
late String name; //不加late会报错,因为没有构造函数初始化变量
late int age;
void setName(String name, int age){
this.name = name;
this.age = age;
}
String getName(){
return "${this.name} --- ${this.age}";
}
}
required关键字
最开始@required是注解。现在作为内置修饰符
主要用于允许根据需要标记任何命名参数(函数或类),使得他们不为空
age和sex是必须传入的命名参数
String printInfo(String username, {required int age,required String sex}){
return "$username -- $sex -- $age";
}
调用
printInfo("123", age:17, sex:"1")
class Person{
String name;
int age;
Person({required this.name, required this.age}); //构造函数参数必须传入,初始化必须指定
}
class Person{ //name可传可不传,age必须传,因此name要用String?表示可空
String? name;
int age;
Person({this.name, required this.age});
}
Dart常量及性能优化
final和const
const声明的常量是在编译时确定的,永远不会改变
final声明的常量允许声明后再赋值,赋值后不可变,final常量是在运行时确定的
final仅有const的编译时常量特性,最重要的是它是运行时常量,且final是惰性初始化,即在运行时第一次使用前才初始化。
dart:core库中identical函数用法:检查两个引用是否指向同一个对象。
bool identical(
Object? a,
Object? b
)
const关键词在多个地方创建相同的对象的时候,内存中只保留了一个对象
void main() {
var o1 = Object();
var o2 = Object();
print(identical(o1, o2)); //false
print(identical(o1, o1)); //true
//共享了存储空间
var o3 = const Object(); //实例化常量构造函数
var o4 = const Object();
print(identical(o3, o4)); // true
const a = [2];
const b = [2];
print(identical(a, b)); //true 因为a和b值相同 且都为const常量,所以他们共享存储空间
}
常量构造函数
1、常量构造函数用const修饰
2、const构造函数必须用于成员变量都是final的类
3、如果实例化时不加const修饰符,即使调用的是常量构造函数,实例化的对象也不是常量实例。
4、实例化常量构造函数的时候,多个地方创建这个对象,如果传入的值相同,只会保留一个对象
5、flutter中const修饰不仅仅是节省组件构建时的内存开销,Flutter在需要重新构建组件的时候,重新构建没有任何意义,因此Flutter不会重新构建const组件
class Container{
int width;
int height;
Container({required this.width, required this.height});
}
main(){
var c1 = Container(width: 100, height: 100);
var c2 = Container(width: 100, height: 100);
print(identical(c1, c2)); //false 意味着两份开销
}
class Container{
final int width;
final int height;
const Container({required this.width, required this.height});
}
main(){
var c1 = const Container(width: 100, height: 100);
var c2 = const Container(width: 100, height: 100);
print(identical(c1, c2)); //true
print(identical(Container(width:100, height: 100), Container(width: 90, height: 100))); //值不一样了 所以是false
}