前言
Dart 是一门现代化编程语言,自2.12版本开始引入空安全的特性,从根本上解决了因变量或对象引用为null而引发的问题。简单来说空安全就是做了一个提前的预防措施,将运行时可能存在的潜在空值引用错误提前到了编译期检查,便于开发者进行修改。
一、空值问题
空值在某些场景下是必要的,但空值的引用不当可能会导致程序崩溃或导致产生难以调试的错误。如在访问或引用 null 的属性或方法时会导致空指针异常。
空值的产生:
- 声明变量时未进行初始化
- 引用不存在的对象
- 赋了null值
二、空安全的定义
空安全是一种编程语言特性,用于防止因变量或对象引用为null而出现的错误。能够在编译期捕捉到潜在的空指针异常,从而减少运行时错误的发生。
需要注意:
- 空安全是在编译期防止意外访问null出现的错误。
- 空安全通过对非空变量的未被初始化或以 null 初始化的情况进行标记,把潜在的 运行时错误 转变成了 编辑时 的分析错误。
三、空安全的原则
3.1 默认不可空
在声明变量时,默认是不可为空的,除非将变量显示声明为可空类型,否则它一定是非空的。
示例:
3.2 安全可靠
Dart 的空安全是非常可靠的。如果类型系统推断出某个变量不为空,那么它 永远 不为空。
四、空安全的核心理念
- 编译期检查来确保不会去轻易的尝试使用null值。
- 非空类型:默认情况下,Dart中的所有类型都是非空的,也就是说他们不能为空值(null)。
- 例如:int 表示一个非空的整数。
- 可空类型:在支持空安全的编程语言中可以根据需要将变量显式声明为可空类型。
- 例如:int? (Dart中)
五、空安全的优势
- 减少运行时错误:对编译期代码的检查,提前捕获到潜在的空指针错误,有效的减少了运行时出现的空引用错误
- 提高代码质量:减少了大量的空值检查操作,从而简化了代码,提高了代码的质量。
- 增强团队协作:有助于团队成员更好地理解代码意图,当每个人都遵循相同空安全规则时,就能够提高团队的协作能力
- 优化性能:避免了不必要防御性编程,使代码更加简洁高效。
六、引入空安全后类型系统的变化
引入前:
从图中可以看出,Null 类型是所有类型的子类,意味着所有类型都可以为Null。
引入后:
引入后可以明显的发现Null类型不再是其它类型的子类,意味着所有类型都不可为空。
七、空安全操作符
7.1 空合并运算符(??)
当左侧表达式为 null 时,使用右侧表达式的值作为默认值。
示例:
Dart
void main() {
int? a;
print(a); // 输出:null
print(a.runtimeType); // 输出:Null
int b = a ?? 9; // 左侧表达式为空,b应该为9
print(b); // 输出:9
}
7.2 空赋值运算符
只有在左侧表达式为 null 时,才执行赋值操作。
示例:
Dart
void main() {
int? a;
print(a); // 输出:null
print(a.runtimeType); // 输出:Null
a = a ?? 9; // 左侧表达式为空,进行赋值操作
print(a); // 输出:9
}
7.3 安全调用操作符
允许安全地访问对象的属性或调用方法,如果对象为 null ,则返回 null。
示例:
Dart
void main() {
// 空感知方法调用
GetData c = GetData();
print(c.runtimeType); // GetData
feelNull(c); // 传入 GetData,输出:2
GetData? d;
print(d.runtimeType); // 输出:Null
feelNull(d); // 传入null,输出:null
}
void feelNull (GetData? data) {
data?.name;
// 空感知方法调用,如果data为空,则返回null,否则调用getNum() 函数
var result = data?.getNum(1);
print(result);
}
class GetData {
String? name;
int getNum(int i){
return i+1;
}
}
7.4 断言操作符(!)
用于给表达式断言,断言其不能为空,若为空则会在运行时报错。
八、延迟初始化(late)
用于延时初始化变量,若未进行初始化,则会运行时报错。
适用场景: 无法在定义时进行初始化,并且又想避免使用?.
九、required关键字
required关键字主要用于防止忘记设置必要的参数。常用于函数参数与构造函数参数,确保必须传入这些参数。
9.1 函数参数中的使用
用于在调用函数时必须要求传入参数,如果不传入提前报错提示。
Dart
void main() {
hello(i: 520); // 输出:hello,Dart! 520
}
void hello({required int i}){
String name = 'Dart';
print('hello,$name! $i');
}
不传入时:
9.2 构造函数参数中使用
Dart
class Car{
void introduce({required String name, String color = 'yellow'}){
print('$name is $color');
}
}
void main() {
Car truck = Car();
truck.introduce(name: 'truck'); // 输出:truck is yellow
}
未传入参数时:
十、总结
本小节从空值问题出发,首先介绍了空值的影响,其次介绍了空安全的定义与原则,然后介绍了空安全的运算符,最后介绍了延迟初始化(late)与关键字required的使用。