空安全版本
空安全(Sound null safety)是 Dart 2.12 中新增的一项特性,空安全特性并不是 Dart 独有的, 健全的空安全已在 Dart 2.12 和 Flutter 2 中可用。
空安全的作用
1.可以将原本运行时的空值引用错误将变为编辑时的分析错误; 2.增强程序的健壮性,有效避免由Null而导致的崩溃; 3.健全性带来的所有优势——更少的 BUG、更小的二进制文件以及更快的执行速度。
空安全修复空引用错误的原理
从类型理论的角度来说,Null 类型被看作是所有类型的子类(如图),
类型会定义一些操作对象,包括 getters、setters、方法和操作符,在表达式中使用。 如果是 List 类型,您可以对其调用 .add() 或 []。 如果是 int 类型,您可以对其调用 +。 但是 null 值并没有它们定义的任何一个方法。 所以当 null 传递至其他类型的表达式时,任何操作都有可能失败。 这就是空引用的症结所在—所有错误都来源于尝试在 null 上查找一个不存在的方法或属性。
空安全通过修改了类型的层级结构,从根源上解决了这个问题。Null 类型仍然存在,但它不再是所有类型的子类(如图)。
既然 Null 已不再被看作所有类型的子类,那么除了特殊的 Null 类型允许传递 null 值,其他类型均不允许。 我们已经将所有的类型设置为默认不可空的类型。如果您的变量是 String 类型,它必须包含一个字符串。这样一来,我们就修复了所有的空引用错误。
空安全的特性
将类型分为了非空类型和可空类型,
String a;//a为非空类型,默认非空
String? a;//a为可空类型
非空类型赋值为空就会报错;
?的原理
本质上,这和定义了一个原有类型加 Null 的 组合类型 没有什么区别。所以如果 Dart 包含完整的组合类型定义,那么 String? 就是 String|Null 的缩写。 在此次改动中,将所有的可空类型作为基础类型的超类。你也可以将 null 传递给一个可空的类型,即 Null 也是任何可空类型的子类: 对于可空类型,只允许访问同时在原有类型及 Null 类下同时定义的方法和属性。所以只有 toString()、== 和 hashCode 可以访问。因此,你可以将可空类型用于 Map 的键值、存储于集合中或者与其他值进行比较,仅此而已。 dart中的类型提升可以将可空类型提升至非空类型,这样可用的属性方法可以很多。空安全中类型提升更加智能,return、break、throw 以及任何可能提早结束函数的方式,都被考虑进来,如果代码中判断了一个可空的变量是否不为 null,进行到下一步后 Dart 就会将这个变量的类型提升至非空的对应类型。
空安全移除了隐式转换:
可选参数必须具有默认值。 如果一个可选位置参数或可选命名参数没有传递内容,Dart 会自动使用默认值进行填充。在未指定默认值的情况下,默认的 默认值为 null,如此一来,非空类型的参数就要出事了。所以,如果你需要一个可选参数,要么它是可空的,要么它的默认值不为 null。 所以在空安全推出之际完全移除了隐式转换。
避空运算符?.
Dart 的避空运算符 ?. ,根据运行时的语义化规定,如果接收者是 null,那么右侧的属性访问就会被跳过,表达式将被作为 null 看待。
// Without null safety:
String notAString = null;
print(notAString?.length);
空安全中,在链式方法调用中使用避空运算符时,如果接收器被判断为 null,那么 整个链式调用的剩余部分都会被截断并跳过
// Using null safety:
showGizmo(Thing? thing) {
print(thing?.doohickey?.gizmo);//因为会被截断,所以第二个问号是没有必要的
}
!修饰符
空断言:表示确保该表达式不会为空,如果为空会报错,引起空指针异常; 一个作为后缀的感叹号标记 (!) 会让左侧的表达式转换成其对应的非空类型
late
由于在空安全中声明的变量默认是非空类型,所以必须赋初始值。但如果不想这么早赋初始值,可以用late(必须确保在使用前赋值,否则会报错)
late String description;
想给一个非空变量赋值 null 要如何处理?
如果想给一个非空变量赋值 null 要如何处理?只需在类型后面添加 ?
String? name = null;
print('name length:${name?.length}');
还可以使用操作符 !
String? name = null;
print('name length:${name!.length}');
还可构造出类型提升,使该变量类型提升为非空类型
int getLength(String? str) {
// 此处报错,因为 str 可能为空
return str.length;
}
//这个例子中,因为 str 可能为空,所以使用 str.length 会提示错误,通过类型提升我们可以这样修改:
int getLength(String? str) {
// 判断 str 为空的场景 str 提升为非空类型
if (str == null) return 0;
return str.length;
}
如何迁移空安全(概述,详见https://dart.cn/null-safety)
迁移前
1.原则:先迁移依赖关系中的处于最末端的依赖。 2.切换至最新的稳定版本 的 Dart SDK 或 Flutter SDK 3.通过以下命令检查你的 package 的迁移状态:如果你看到所有依赖都已支持空安全,就意味着你可以开始迁移了。
dart pub outdated --mode=null-safety
迁移
自己动手迁移(可使用迁移工具,它可以帮你处理大多数可推导的变更。)
1.编辑 package 的 pubspec.yaml 文件,将最低 SDK 版本设置到至少为 2.12.0:
environment:
sdk: '>=2.12.0 <3.0.0'
重新生成 package 的配置文件:
dart pub get
在版本最低是 2.12.0 的 SDK 上运行 dart pub get 时,会将每个 package 的默认 SDK 最低版本设定为 2.12,并且默认它们已经迁移至空安全。
2.在你的 IDE 上打开package 。 你也许会看到很多错误,没关系,让我们继续。
利用分析器来辨析静态错误,逐个迁移 Dart 文件。 按需添加 ?、!、required 以及 late 来消除静态错误。 3. 分析 更新你的 package(在 IDE 或命令行工具中使用 dart pub get)后在 IDE 或命令行工具中对你的代码进行 静态分析:
dart pub get
dart analyze
|