Dart语言的特点:
- 同时支持JIT(Just In Time,即时编译)和AOT(Ahead Of Time,运行前编译)
JIT在运行时即时编译,可以动态下发和执行代码,开发测试效率高,但是运行速度和执行性能会因为运行时即时编译受到影响。Flutter热重载就是基于此特性实现。
AOT即提前编译,可以生成被直接执行的二进制代码,运行速度快,执行性能高,但是每次执行前都需要重新编译,开发测试效率低。发布时使用AOT,使应用运行速度快、执行性能好。
- 内存分配和垃圾回收
Dart VM的内存分配策略比较简单,创建对象时只需要在堆上移动指针,内存增长始终是线性的,省去了查找可用内存的过程。
Dart的垃圾回收,则是采用了多生代算法。新生代在回收内存时采用“半空间”机制,触发垃圾回收时,Dart会将当前“半空间”中的“活跃”对象拷贝至备用空间,然后整体释放当前空间的所有内容。回收过程中,Dart只需要操作少量的“活跃”对象,没有引用的大量“死亡对象”则被忽略,这样的回收机制很适合Flutter框架中大量Widget销毁重建的场景。
- 单线程
Dart是单线程模型天然不存在资源竞争和状态同步的问题。这就意味着,一旦某个函数开始执行,就将执行到这个函数结束,而不会被其他Dart代码打断。
所以,Dart中并没有线程,只有Isolate(隔离区)。Isolate之间不会共享内存,就像几个运行在不同进程中的Worker,通过事件循环(Event Looper)在事件队列(Event Queue)上传递消息通信。
- 无需单独的声明式布局语言
Dart声明式编程布局易于阅读和可视化,使得flutter并不需要类似JSX或XML的声明式布局语言。
开发过程中也不需要可视化界面构建器,因为热重载可以让我们立即在手机上看到运行效果。
一、数据类型
1.1、Numbers
-
超类num中定义了基本运算符,比如:
+
、-
、*
、/
等,还定义了abs()
、ceil()
和floor()
等方法。
//定义数值型变量n
num n = 1;
//num是int和double的超类,所以可以被赋值为int和double值
n = 3;
n = 3.1415;
-
int:长度不超过64位,具体取值范围依赖于不同的平台。整型还支持位运算操作,比如:移位(
<<
、>>
和>>>
)、补码 (~
)、按位与 (&
)、按位或 (|
) 以及按位异或 (^
)。
//定义int类型变量
int a = 1;
//定义可变类型b,通过字面量推断出b是int类型
var b = 2;
//a和b在定义后,类型已经被固定为int,赋值double会报错:A value of type 'double' can't be //assigned to a variable of type 'int'.
a=3.1415;
- double:长度为64位。
//定义double类型变量
double c = 3.1415;
//定义可变类型d,通过字面量推断出d是double类型
var d = 3.1415;
//整数字面量将在必要的时候自动转换成浮点字面量(Dart2.1之后)
c = 100;
-
在dart:math类中定义了更多操作方法,如Random。
注:数据类型在native和web平台有一些差异,具体可参考官网说明, Numbers in Darthttps://dart.dev/guides/language/numbers
1.2、Strings
Dart中的字符串是包含了UTF-16编码的字符序列。字符串是不可变的对象,也就是说字符串可以创建但是不能被修改。
- 可以使用单引号或者双引号来创建字符串字面量
var str1='Hello,world!';
var str1="Hello,world!";
- 可以使用三个单引号或者三个双引号来创建多行字符串
var s1 = '''
You can create
multi-line strings like this one.
''';
var s2 = """This is also a
multi-line string.""";
- 字符串插值
//字符串插值
var s = '字符串插值';
assert('Dart 有$s,使用起来非常方便。' == 'Dart 有字符串插值,使用起来非常方便。');
assert('使用${s.substring(3, 5)}表达式也非常方便。' == '使用插值表达式也非常方便。');
- 原始字符串
var s = r'在 raw 字符串中,转义字符串 \n 会直接输出 “\n” 而不是转义为换行。';
- 字符串操作
var s1 = 'hello word!';
var s2 = 'hello word!';
//判断两个对象的内容是否一样
assert(s1 == s2); //true
//字符串拼接
var s3 = s1+" "+s2; //hello word! hello word!
//字符串重复
var s4 = s1*2; //hello word!hello word!
//根据下标取字符
var s5 = s1[0]; //h
//字符串长度
s1.length
//是否包含子字符串
s1.contains("o");
//字符串截取
s1.substring(0,2);
//字符串开头
s1.startsWith("he");
//字符串结尾
s1.endsWith("d!");
//字符串为空
s1.isEmpty;
//字符串非空
s1.isNotEmpty;
//字符串拆分
s1.split(' ');
//字符串转小写
s1.toLoWerCase();
//字符串转大写
s1.toUpperCase();
//字符串去除前后空格
s1.trim();
//字符串去除左侧空格
s1.trimLeft();
//字符串去除右侧空格
s1.trimRight();
//字符串字符替换
s1.replaceAll("a","b");
...
- 字符串和数值之间转换
// String -> int
var one = int.parse('1');
assert(one == 1);
// String -> double
var onePointOne = double.parse('1.1');
assert(onePointOne == 1.1);
// int -> String
String oneAsString = 1.toString();
assert(oneAsString == '1');
// double -> String 【toStringAsFixed是按照四舍五入的规则截取的】
String piAsString = 3.14159.toStringAsFixed(2);
assert(piAsString == '3.14');
1.3、bool
Dart 使用 bool
关键字表示布尔类型,布尔类型只有两个对象 true
和 false
,两者都是编译时常量。
bool b=true;
//使用字面量创建变量
var b1 = true;
Dart 的类型安全不允许你使用类似 if (nonbooleanValue)
或者 assert (nonbooleanValue)
这样的代码检查布尔值。
1.4、list
Dart中list表示的更像是Java中的数组,有序并且元素可重复。
- 字面量创建变量
//这里 Dart 推断出 `list` 的类型为 `List<int>`,如果往该数组中添加一个非 int 类型的对象则会报错。
var list = [1, 2, 3];
- 指定泛型创建变量
var list = <String>["张三", "王五", "张三", "李四"];
- 常规操作
// 创建一个空数组 & 数组判空
var fruits = <String>[];
assert(fruits.isEmpty);
// 添加元素
fruits.add('kiwis');
//添加多个元素 & 获取数组长度
fruits.addAll(['grapes','apples', 'bananas']);
assert(fruits.length == 4);
// 获取数组中元素的下标 & 移除元素
var appleIndex = fruits.indexOf('apples');
fruits.removeAt(appleIndex);
assert(fruits.length == 3);
// 清空数组
fruits.clear();
assert(fruits.isEmpty);
- Dart 在 2.3 引入了 扩展操作符(
...
)和 空感知扩展操作符(...?
),如下:
//你可以使用扩展操作符(...)将一个 List 中的所有元素插入到另一个 List 中
var list = [1, 2, 3];
var list2 = [0, ...list];
assert(list2.length == 4);
//如果扩展操作符右边可能为 null ,你可以使用 null-aware 扩展操作符(...?)来避免产生异常:
var list;
var list2 = [0, ...?list];
assert(list2.length == 1);
1.5、set
在 Dart 中,set与list刚好相反是一个⭐无序的,元素唯一的⭐集合。
- 字面量创建变量
//与list差别在于,set是使用{}来表示
var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};
- 明确类型创建变量
//指定泛型:
var names = <String>{};
//还可以这样定义set
Set<String> names = {};
- 常规操作
// 创建set
var ingredients = <String>{};
// 添加多个元素
ingredients.addAll(['gold', 'titanium', 'xenon']);
assert(ingredients.length == 3);
// 添加一个元素重复的元素,set集合没有变化
ingredients.add('gold');
assert(ingredients.length == 3);
// 移除一个元素
ingredients.remove('gold');
assert(ingredients.length == 2);
// 传入列表创建set集合
var atomicNumbers = Set.from([79, 22, 54]);
1.6、map
Map 是用来关联 keys 和 values 的对象,其中键和值都可以是任何类型的对象,每个【键】只能出现一次但是【值】可以重复出现多次。
- 字面量创建变量
//字面量声明
var gifts = {
// Key: Value
'first': 'partridge',
'second': 'turtledoves',
'fifth': 'golden rings'
};
- 明确类型创建变量
var gifts = Map<String, String>();
gifts['first'] = 'partridge';
gifts['second'] = 'turtledoves';
gifts['fifth'] = 'golden rings';
- 常规操作
//定义
var nobleGases = {54: 'xenon'};
assert(nobleGases[54] == 'xenon');
// 判断某个key是否存在
assert(nobleGases.containsKey(54));
//通过key,移除某个键值对
nobleGases.remove(54);
二、函数
2.1、函数的定义
Dart中函数也是一个对象,其类型为Function
。
- 函数定义和调用
void main() {
//函数调用
var result = add(1, 2);
}
///定义函数
int add(int a, int b) {
return a + b;
}
- 箭头函数
语法 => 表达式
是 { return 表达式; }
的简写, =>
有时也称之为 箭头 函数。
//箭头函数
int add(int a, int b) => a + b;
- 返回值
所有的函数都有返回值。没有显示返回语句的函数最后一行默认为执行 return null;
。
foo() {}
assert(foo() == null);
2.2、函数的参数
函数可以有两种形式的参数:必要参数 和 可选参数。可选参数可以是 命名的 或 位置的。
- 必要参数
必要参数直接定义在参数列表前面,必要参数在调用函数时是必传的。
// a和b都是必要参数
void fun(int a, int b) {}
//调用
fun(1,2);
- 命名参数
使用{}
将一系列参数包裹起来表示命名参数,定义在参数列表中必要参数的后面。
//c是必要参数,a和b是命名参数
void fun(int c, {int? a, int? b}) {}
//调用
//命名参数可不传或者只传其中一个
fun(3);
fun(3, a: 1);
fun(3, b: 2);
//命名参数传参位置可变
fun(3, a: 1, b: 2);
fun(3, b: 2, a: 1);
可以使用 required
来特殊标识一个命名参数是必须的参数:
//c是必要参数,a和b是命名参数,但调用时必须为b参数传值
void fun(int c, {int? a, required int b}) {}
//调用
//a可以不传值,但是b必须传值
fun(3, b: 2);
fun(3, a: 1, b: 2);
- 位置参数
使用[]
将一系列参数包裹起来表示位置参数,同样需要定义在必要参数的后面。
//c是必要参数,a和b是位置参数
void fun(int c, [int? a, int? b]) {}
//调用
fun(3);
fun(3, 1);
fun(3, 1, 2);
- 参数默认值
可以用=
为函数的命名参数和位置参数定义默认值,默认值必须为编译时常量,没有指定默认值的情况下默认值为null
。
void fun(int c, {int a=1, int b=2}) {}
void fun(int c, [int a=1, int b=2]) {}
- 参数是值传递还是引用传递? ⭐Dart中基本类型参数是值传递,对象参数是引用传递。⭐
2.3、main函数
main()
函数作为Dart程序的入口函数,返回值为void
并且有一个List<String>
类型的可选参数。
2.4、函数是一级对象
一级对象是说它可以像其他对象一样使用,例如:可以被赋值给一个变量,可以作为参数传递给函数,可以作为值被函数返回。
void main() {
// 1.将函数赋值给一个变量
var v = foo;
// 2.将函数作为另一个函数的参数
test(foo);
// 3.将函数作为另一个函数的返回值
var func = getFunc();
func('kobe');
}
// 1.定义一个函数
void foo(String name) {
print('传入的值:$name');
}
// 2.将函数作为另外一个函数的参数
void test(Function func) {
func('Aliu');
}
// 3.将函数作为另一个函数的返回值
Function getFunc() {
return foo;
}
2.5、匿名函数
没有名字的方法,称之为 匿名函数、 Lambda 表达式 或 Closure 闭包。可以将匿名方法赋值给一个变量然后使用它,比如将该变量添加到集合或从中删除。
//定义匿名函数并复制给变量
var fun = (String name) {
print(name);
};
var list = ["apple", "banana", "orange"];
//将匿名函数作为参数传给forEach函数,在forEach中被调用。
list.forEach(fun);
//输出结果:
apple
banana
orange
2.6、词法作用域
Dart中变量的作用域:
- 定义在函数内的变量只能在定义它的函数大括号
{}
内访问。 - 定义在文件中的变量(也叫顶层变量),整个文件中都能访问到。
//顶层变量
bool topLevel = true;
void main() {
//函数内变量,相当于Java中的局部变量或者临时变量。
var insideMain = true;
void myFunction() {
var insideFunction = true;
assert(topLevel);
assert(insideMain);
assert(insideFunction);
}
}
2.7、词法闭包
闭包 即一个函数对象,即使函数对象的调用在它原始作用域之外,依然能够访问在它词法作用域内的变量。
注:这个貌似就是一个匿名函数,不知道理解的对不对。
//sum返回的是一个函数,函数在sum中定义的
Function sum(int addBy) {
return (int i) => addBy + i;
}
void main() {
//add2 = (int i) => 2 + i;
var add2 = sum(2);
//*在main函数中可以调用到sum中定义的函数闭包*
assert(add2(3) == 5);
}
三、运算符
3.1、算术运算符
运算符 | 描述 |
---|---|
+ | 加 |
– | 减 |
-表达式 | 一元负, 反转表达式的符号 |
* | 乘 |
/ | 除 |
~/ | 除并取整 |
% | 取模 |
++ | 自增 |
-- | 自减 |
3.2、关系运算符
运算符 | 描述 |
---|---|
== | 相等 |
!= | 不等 |
> | 大于 |
< | 小于 |
>= | 大于等于 |
<= | 小于等于 |
3.3、类型判断运算符
运算符 | 描述 |
---|---|
as | 类型转换(也用作指定类前缀) |
is | 如果对象是指定类型则返回 true |
is! | 如果对象是指定类型则返回 false |
3.4、赋值运算符
运算符 | 描述 |
---|---|
= | 给变量赋值 |
??= | 当变量为null时,给变量赋值;当变量有值时,保持原有的值 |
3.5、逻辑运算符
运算符 | 描述 |
---|---|
! | 逻辑非,对表达式结果取反 |
|| | 逻辑或 |
&& | 逻辑与 |
3.6、按位和移位运算符
运算符 | 描述 |
---|---|
& | 按位与 |
| | 按位或 |
按位异或 | |
~ | 按位取反 |
<< | 位左移 |
>> | 位右移 |
>>> | 无符号右移 |
3.7、条件表达式
-
?:
条件 ? 表达式 1 : 表达式 2
如果条件为 true,执行表达式 1并返回执行结果,否则执行表达式 2 并返回执行结果。 -
??
表达式 1 ?? 表达式 2
如果表达式 1 为非 null 则返回其值,否则执行表达式 2 并返回其值。
3.8、级联运算符
级联运算符(..
, ?..
)可以让你在同一个对象上连续调用多个对象的变量或方法。
..
class Person{
String? name;
int? age;
}
void main() {
var person = Person()
..name = "张三"
..age = 20;
}
?..
注: ?..
与..
相比添加了空类型检查,会判断左侧对象不为空时才执行。
四、异常
4.1、抛出异常
在Dart中使用throw
关键字抛出异常,异常可以是系统定义的异常类型,可以是自定义的异常类型,也可以是任意的对象。
4.2、捕获异常
在Dart中可以使用on
或catch
来捕获异常,使用on
来指定异常类型,使用catch
来捕获异常对象,两者可同时使用。
try {
} on OutOfLlamasException {
// A specific exception
} on Exception catch (e) {
// Anything else that is an exception
} catch (e) {
// No specified type, handles all
}
catch
有两个参数,第一个是异常对象,第二个是栈信息(StackTrace
)。
try {
} catch (e,s) {
}
在catch
中使用关键字rethrow
可以将捕获的异常再次抛出。
try {
} catch (e) {
rethrow;
}
4.3、Finally
无论是否抛出异常,finally
语句始终执行,如果没有指定 catch
语句来捕获异常,则异常会在执行完 finally
语句后抛出
try {
} catch (e) {
}finally{
}
4.4、系统提供的异常类型
Dart提供的异常主要分为Exception
和Error
两大类,下面枚举一下两类具体包含的异常类型:
- Exception
子类 | 描述 |
---|---|
DeferredLoadException | 延迟库加载失败异常 |
FormatException | 格式处理异常 |
IOException | IO操作异常 |
IsolateSpawnException | Isolate创建异常 |
NullRejectionException | 空值异常 |
OSError | 操作系统异常 |
TimeoutException | 异步超时异常 |
注:IntegerDivisionByZeroException
用UnsupportedError
代替
- Error
子类 | 描述 |
---|---|
AbstractClassInstantiationError | 抽象类实例化错误(Dart2中改为编译时错误) |
ArgumentError | 参数错误 |
AssertionError | 断言错误 |
AsyncError | 异步错误 |
ConcurrentModificationError | 并发修改错误 |
CyclicInitializationError | 延迟初始化错误 |
FallThroughError | Switch中最后一个case未break时抛出异常 |
JsonUnsupportedObjectError | Json不支持错误 |
NoSuchMethodError | 没有找到方法错误 |
NullThrownError | 抛出空异常错误 |
OutOfMemoryError | 内存溢出错误 |
RemoteError | 隔离错误 |
StackOverflowError | 堆栈溢出错误 |
StateError | 状态错误 |
TypeError | 动态类型错误 |
UnimplementedError | 未实现错误 |
UnsupportedError | 不支持错误 |
注:原有CastError
用TypeError
代替。
五、类
Dart是支持基于mixin
继承机制的面向对象语言,所有对象都是一个实例。在Dart中,虽然类是单继承,但是因为mixin
继承机制,可以实现多继承的效果。
5.1、类成员
类的成员由方法和属性组成,成员由类的实例化对象访问。成员访问符有.
和?.
两种,其中?.
比.
多了空对象判断(也可以叫成员条件访问符)。
class Person {
int? age;
String? name;
}
void main() {
var person = Person()
..name = "张三"
..age = 20;
// .
print("${person.name},${person.age}");
printString(person);
}
void printString(Person? person){
// ?.
print("${person?.name},${person?.age}");
}
5.2、构造函数
- 默认构造函数
如果没有声明构造函数,会默认生成一个无参构造函数,并且调用父类的无参构造函数。
- 构造函数不被继承
子类不会继承父类的构造函数。
- 声明构造函数
class Point {
double x = 0;
double y = 0;
//变量名重复时,使用 `this` 关键字引用类成员变量
Point(double x, double y) {
this.x = x;
this.y = y;
}
}
class Point {
double x = 0;
double y = 0;
//可以简写成这样
Point(this.x, this.y);
}
- 命名式构造函数
因为Dart不支持方法的重载,所以我们不能创建相同名称的构造方法,当需要创建多个构造函数的时候,就可以使用命名式构造函数。使用命名式构造函数好处是可以更清晰的表达你的意图。
class Person {
String? name;
int? age;
Person(this.name, this.age) {}
// 命名构造方法
Person.withArguments(String name, int age) {
this.name = name;
this.age = age;
}
}
void main() {
// 创建对象
var p1 = new Person("李四", 20);
var p2 = new Person.withArguments('张三', 18);
}
- 初始化列表
初始化列表用于初始化实例变量,执行在构造函数的函数体之前。格式如下:
import 'dart:math';
class Point {
final double x;
final double y;
final double distance;
/// :属性=表达式,属性=表达式
Point(double a, double b)
: x = a,
y = b,
distance = sqrt(a * a + b * b) {
//构造函数的函数体执行的时候,x,y和distance已经完成了初始化
print("$x $y $distance");
}
}
void main() {
var p = Point(3, 4);
}
- 重定向构造函数
重定向构造函数用于在一个构造函数中去调用另一个构造函数。
class Person {
String name;
int age;
Person(this.name, this.age);
//重定向构造函数
Person.fromName(String name) : this(name, 0);
}
- 常量构造函数
将类的所有的属性声明为final
,并在类的构造函数前加上const
关键字,这样就声明出了一个常量构造函数。常量构造函数可以将类对象实例改为编译时常量。当我们传参相同时,拿到的对象实例是同一个。
class Point {
final double x, y;
//常量构造函数
const Point(this.x, this.y);
}
void main() {
//生成常量对象时,需要添加const关键字
var p1 = const Point(3, 4);
//变量带有const关键字的话,创建对象时可以省略const
const p2 = Point(3, 4);
//未加const关键字,生成的是非常量对象
var p3 = Point(3, 4);
//true
print(identical(p1, p2));
//false
print(identical(p1, p3));
}
- 工厂构造函数
使用factory关键字标识的构造方法是工厂构造方法,工厂构造方法需要我们通过代码主动的返回一个实例对象。
class Logger {
final String name;
static final Map<String, Logger> _cache = <String, Logger>{};
factory Logger(String name) {
return _cache.putIfAbsent(name, () => Logger._internal(name));
}
Logger._internal(this.name);
void log(String msg) {
if (!mute) print(msg);
}
}
注: V putIfAbsent(K key,V ifAbsent())
方法,如果map
中有key
对应的值则返回该值,否则调用ifAbsent
方法获取新值,并将key
与新值存入map
,然后返回新值。
工厂构造函数可以用来实现单例模式:
void main() {
XXUtils.getInstance().printStr("hello world !");
}
class XXUtils {
//工厂构造函数无法访问this,所以这里要用static
static XXUtils? _cache;
//工厂方法构造函数,关键字factory
factory XXUtils.getInstance() => XXUtils._cache ??= XXUtils._newObject();
//定义一个命名构造函数用来生产实例(私有化的构造函数)
XXUtils._newObject();
//随便定义一个方法
void printStr(String name){
print(name);
}
}
5.3、抽象类
抽象类是被关键字abstract
标识的类,抽象类常用于声明接口方法,抽象类正常情况下无法实例化,除非提供工厂构造函数。
abstract class Animal {
//非抽象方法
void eat(){
print("它爱吃什么?");
}
//抽象方法
void run();
}
class Dog extends Animal {
Dog();
//实现抽象方法
@override
void run() {
print("小狗跑的很快");
}
}
void main() {
var dog=Dog();
//可以调用到抽象类中的非抽象方法
dog.eat();//它爱吃什么?
//调用抽象类中抽象方法的实现
dog.run();//小狗跑的很快
}
5.4、隐式接口
Dart中每个类都隐式的定义了一个接口,这个接口中包含了所有这个类的实例成员以及这个类所实现的其它接口,使用implements
关键字实现一个类的时候,实现的就是其隐式接口。
class A {
var a = 10;
void methodA1() {}
void methodA2() {}
}
///需要实现A中所有的成员
class B implements A {
@override
int a;
B(this.a);
@override
void methodA1() {
// TODO: implement methodA1
}
@override
void methodA2() {
// TODO: implement methodA2
}
}
5.5、继承
子类可以复写父类方法,子类可以通过super
关键字访问父类,子类继承父类中所有的变量和方法(除构造函数以外)。
class Animal {
//非抽象方法
void eat() {
print("它爱吃什么?");
}
}
class Dog extends Animal {
Dog();
//复写父类方法
@override
void eat() {
//使用super关键字调用父类方法
super.eat();//它爱吃什么?
//自己的实现内容
print("小狗爱吃骨头");
}
}
void main() {
var dog = Dog();
dog.eat();
}
5.6、枚举类
枚举类用于定义一些固定数量的常量值。
//使用enum关键字定义枚举类
enum MyColor { red, blue, yellow }
void main() {
print(MyColor.red.runtimeType); //MyColor.red
print(MyColor.red.index); //0
print(MyColor.red.name); //red
print(MyColor.red.name.runtimeType); //String
print(MyColor.red.hashCode); //597084885
}
枚举类型有如下两个限制:
- 枚举不能成为子类,也不可以mixin,你也不可以实现一个枚举。
- 不能显式地实例化一个枚举类。
5.7、mixin
使用关键字mixin定义的类是mixin类。mixin类变相实现了类的多重继承。
mixin Runner {
void run() {
print('在奔跑');
}
}
mixin Flyer {
void fly() {
print('在飞翔');
}
}
//通过with关键字使用mixin类
class SuperMain with Runner, Flyer {}
void main() {
var superMan = SuperMain();
//可以调用到mixin类中的方法
superMan.run();//在奔跑
superMan.fly();//在飞翔
}
与implements
相比,mixin方式不需要实现接口中的方法。
六、泛型
泛型从字面的意思理解为一种宽泛的类型,使用<>
和类型参数(E
、T
、S
、K
和 V
等)替代具体的类型。使用泛型定义的类或者方法可以在使用/调用时才指明具体的类型,大大提高了代码的复用程度。
6.1、泛型类
void main() {
//实例化时,指定类型为String
var stringCache=Cache<String>();
stringCache.setByKey("id", "ID123456");
stringCache.getByKey("id");
print(stringCache.runtimeType);//类型是Cache<String>
}
//泛型类
class Cache<T> {
final map = <String, T>{};
T? getByKey(String key) {
return map[key];
}
void setByKey(String key, T value) {
map[key] = value;
}
}
与 Java 不同的是,Java 中的泛型是类型 擦除 的,这意味着泛型类型会在运行时被移除。在 Java 中你可以判断对象是否为 List
但不可以判断对象是否为 List<String>
。而Dart的泛型类型是固化的即便在运行时也会保持类型信息。
- 限制参数化类型
使用 extends
关键字可以限制泛型参数必须是指定类型的子类。
class View {
int? width;
int? height;
}
//View子类
class Image extends View {
//
}
//View子类
class TextView extends View {
//
}
//泛型类,使用extends限定类型必须是View的子类
class ViewWrapper<T extends View> {
//泛型变量
T? mView;
}
void main() {
var vw1 = ViewWrapper<Image>();
var vw2 = ViewWrapper<TextView>();
}
6.2、泛型方法
起初 Dart 只支持在类的声明时指定泛型,现在同样也可以在方法上使用泛型,称之为 泛型方法:
T first<T>(List<T> ts) {
return ts[0];
}
方法 first<T>
的泛型 T
可以在如下地方使用:
- 函数的返回值类型 (
T
) - 参数的类型 (
List<T>
) - 局部变量的类型 (
T tmp
)
七、库和可见性
7.1、可见性
Dart中没有定义与JAVA语言类似的访问控制符。Dart中用有无下划线(_
)来替代JAVA中的public
或 private
访问控制符。
以下划线(_
)开头的成员仅在代码库中可见(相当于private
的)
//公共方法,可以被其它文件引用和访问
int getScreenWidth(){
return 1080;
}
//私有方法,当前文件中可以访问
int _getScreenHeight(){
return 1920;
}
7.2、库
在Dart中我们可以将每个类视为一个Mini Library。
7.2.1、import
import
关键字用来引用一个库文件,格式为:import "<URI>"
,例如:
//导入math库
import 'dart:math';
void main() {
print(sqrt(9));//3
}
- Dart中的库主要分为三种:
类型 | 描述 | URI格式 |
---|---|---|
Dart内置库 | 系统内置的库 | dart:xxxxxx |
远程库 | 发布到pub.dev的库 | package:xxxxxx |
本地库 | 工程目录下的本地库 | xxx/xxx/xxx.dart |
- 在开发规范中要求我们导包时遵守以下三个规则:
- 要把 “dart:” 导入语句放到其他导入语句之前。
- 要把 “package:” 导入语句放到项目相关导入语句之前。
- 要按照字母顺序来排序每个部分中的语句。
//优先导入"dart:"语句,并按照字母顺序排序
import 'dart:async';
import 'dart:html';
//然后导入"package:"语句,并按照字母顺序排序
import 'package:bar/bar.dart';
import 'package:foo/foo.dart';
//最后是项目内库的导入语句,并按照字母顺序排序
import 'foo.dart';
import 'foo/foo.dart';
- 指定前缀 当导入的两个代码库中含有有冲突的标识符时,可以为其中一个库指定前缀。
//导入math库,并指定前缀
import "dart:math" as math;
void main() {
//不能直接调用sqrt函数,必须用前缀math访问sqrt
print(math.sqrt(9));//3
}
- 选择性载入
show
只载入库的某些部分,hide
筛选掉库的某些部分
//只导入math库中的sqrt函数
import "dart:math" show sqrt;
//导入html库中除了HttpRequest之外的其它内容
import "dart:html" as html hide HttpRequest;
void main() {
//可以访问的到sqrt方法
print(sqrt(9));
//报错,访问不到min方法
print(min(8,9));
//报错,HttpRequest未定义
html.HttpRequest();
}
- 延迟加载
deferred as
关键字可以延迟加载库,一般用于优化启动速度。
//延迟加载
import "dart:math" deferred as math;
void main() {
executeSqrt(9);
}
void executeSqrt(int num) async {
//异步等待库加载完成
await math.loadLibrary();
//调用库函数
var result = math.sqrt(num);
print(result);
}
7.2.2、part
part
关键字可以将多个dart合并到一个文件中。
- 定义log_utils.dart文件
//声明当前文件是app_utils.dart文件的一部分
part of 'app_utils.dart';
//打印信息
void logMessage(String message) {
print(message);
}
- 定义device_utils.dart文件
//声明当前文件是app_utils.dart文件的一部分
part of 'app_utils.dart';
///获取屏幕宽度
int getScreenWidth(){
return 1080;
}
///获取屏幕高度
int getScreenHeight(){
return 1920;
}
- 定义app_utils.dart文件
//device_utils和log_utils可以访问到这个库
import 'dart:math';
//合并两部分
part 'device_utils.dart';
part 'log_utils.dart';
① 此时如果导入app_utils.dart
文件,那么相当于同时导入了device_utils.dart
和log_utils.dart
两个文件。
② 在device_utils.dart
声明了part of 'app_utils.dart'
之后,表明device_utils
是app_utils
的一部分,此时如果app_utils
文件顶部导入了某个库,device_utils
是可以访问到这个库的;
注:官方已经不建议使用part
,推荐我们使用export
来替代。
7.2.3、export
import
是将库引入到当前文件使用的话,那么export
就是导出库给其他文件使用。
- 定义device_utils.dart文件
///获取屏幕宽度
int getScreenWidth(){
return 1080;
}
///获取屏幕高度
int getScreenHeight(){
return 1920;
}
- 定义app_utils.dart文件
library app_utils;
//将device_utils文件导出
export 'src/utils/device_utils.dart';
- 使用
//引入app_utils.dart文件
import 'app_utils.dart' ;
void main() {
//可以访问到device_utils中的方法
getScreenWidth();
}
注:规范中要求我们导出(export
)语句作为一个单独的部分放到所有导入(import
)语句之后。
library app_utils;
import 'dart:math';
//在所有import之后
export 'src/utils/device_utils.dart';
export 'xxx';
7.3、library
- Library 的代码位于
lib
目录下,且对于其他 Package 是公开的。 - 你可以根据需要在
lib
下任意创建组织文件结构。 - 按照惯例,实现代码会放在
lib/src
目录下。 lib/src
目录下的代码被认为是私有的。- 其他 Package 应该永远不需要导入
src/...
目录下代码。 - 通过导出
lib/src
目录的文件到一个lib
目录的文件,实现对lib/src
目录中 API 的公开。
案例:
主 Library 文件 shelf.dart
在 lib 目录下,通过 shelf.dart
文件导出 lib/src 目录下的若干文件。
library shelf;
export 'src/cascade.dart' show Cascade;
export 'src/handler.dart' show Handler;
export 'src/hijack_exception.dart' show HijackException;
...
注:show
关键字可以指定具体导出哪些内容,防止导出不必要的API。