▩Dart-空安全(Null Safety)
Posted itzyjr
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了▩Dart-空安全(Null Safety)相关的知识,希望对你有一定的参考价值。
目录
当你选择使用空安全时,代码中的类型在默认情况下是不可空的,这意味着值不能为空,除非你说它们可以为空。
When you opt into null safety, types in your code are non-nullable by default, meaning that values can’t be null unless you say they can be.
本文章涵盖:
- 可空(nullable)和不可空(non-nullable)类型。
- 什么时候添加“?”或“!”来表明可空(nullability)或不可空(non-nullability)。
- 流程分析和类型提升。
late
关键字是如何影响变量和初始化的。- 健全的空安全(sound null safety)。
一、可空和不可空类型(Nullable and non-nullable types)
所有的类型默认都是非空的。例如,如果你有一个String
类型的变量,它将总是包含一个字符串。
如果你想要String
类型能接收null
,则在类型名后加一个“?”。例如,String?
类型可以包含字符串,也可以是null
。
main()
int a;
a = null;// Error:A value of type 'Null' can't be assigned to a variable of type 'int'.
print('a is $a.');
void main()
int? a;
a = null;
print('a is $a.');// a is null.
先看一个错误的情况:
修正如下:
void main()
List<String> aListOfStrings = ['one', 'two', 'three'];
List<String>? aNullableListOfStrings;// List<String>后添加了 ?
List<String?> aListOfNullableStrings = ['one', null, 'three'];// List<String>里面的String后添加了 ?
print('aListOfStrings is $aListOfStrings.');
print('aNullableListOfStrings is $aNullableListOfStrings.');
print('aListOfNullableStrings is $aListOfNullableStrings.');
二、空断言运算符“!”(The null assertion operator(!))
当你非常笃定一个可能为空的类型的表达式一定不为null
,可以在表达式后添加“!”来告诉Dart将它作为非空类型对待。在表达式后添加“!”,告诉Dart,值不会为null
,可以安全地将它赋值给一个非空变量。
注:如果你错了,Dart会在运行时抛出一个异常。所以除非你非常肯定表达式不为空,否则使用“!”将变得不安全。
修正如下:
int? couldReturnNullButDoesnt() => -3;
void main()
int? couldBeNullButIsnt = 1;
List<int?> listThatCouldHoldNulls = [2, null, 4];
int a = couldBeNullButIsnt;
// list中包含null,所以list.first就可能为null
// 第1种解决方法:int? b = listThatCouldHoldNulls.first;
// 第2种解决方法:可以笃定第一个元素不为空,加“!”
int b = listThatCouldHoldNulls.first!;
int c = couldReturnNullButDoesnt()!.abs(); // couldReturnNullButDoesnt()返回-3,一定不为null,加“!”
print('a is $a.');// a is 1.
print('b is $b.');// b is 2.
print('c is $c.');// c is 3.
三、类型提升(Type promotion)
随着健全的空安全(sound null safety),Dart的流程分析已扩展到考虑可空性。不可能包含空值的可空变量被直接视为不可空变量。这种行为称为类型提升。
Dart的类型系统可以跟踪变量被分配和读取的位置,并且可以在任何代码尝试从中读取变量之前验证不可为null
的字段是否已给定值。这个过程称为明确分配(definite assignment)。
main()
String text;// text:不可为null的字段
if (DateTime.now().hour < 12)
text = "It's morning! Let's make aloo paratha!";// (if)不可为空的字段已给定值
else
text = "It's afternoon! Let's make biryani!";// (else)不可为空的字段已给定值
// 此时,text字段变为了不可能为空的变量。所以text后续随意调用,而不用加?或! ——————【类型提升】
print(text);
print(text.length);// 已明确了text的属性,不用text!.length了!
如果注释掉else代码,则text字段不是明确不为空的字段,则得这样修正代码:
String? text;// 添加 ?
if (DateTime.now().hour < 12)
text = "It's morning! Let's make aloo paratha!";
print(text);
print(text!.length);// 这样不报错,但运行时就可能有异常。
可能抛出异常:
Unhandled exception:
Null check operator used on a null value
…
可以为代码添加空检测(null checker)逻辑:
int getLength(String? str)
if (str == null)// Add null check here
return 0;
return str.length;
void main()
print(getLength('This is a string!'));
也可以在检测到null时,抛出异常:
int getLength(String? str)
if (str == null)
throw NullThrownError;
return str.length;
void main()
print(getLength(null));
Unhandled exception: NullThrownError #0 getLength (file:///E:/workplaces/flutter_workplace/hello_dart/bin/hello_dart.dart:5:5) #1 main (file:///E:/workplaces/flutter_workplace/hello_dart/bin/hello_dart.dart:11:9) #2 _delayEntrypointInvocation. (dart:isolate-patch/isolate_patch.dart:297:19) #3 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:192:12)
四、late
关键字
有时变量(类中的字段或顶级变量)应该不可为null
,但不能立即为它们赋值。对于这样的情况,请使用late
关键字。
当你在变量声明前添加late
时,它会告诉Dart以下内容:
- 不要给那个变量赋值。
- 稍后将为其指定一个值。
- 在使用变量之前,你将确保该变量具有值。
如果声明变量为late
,并在赋值之前读取该变量,则会引发错误。
class Meal
1. String _description;
set description(String desc)
_description = 'Meal description: $desc';
String get description => _description;
void main()
final myMeal = Meal();
myMeal.description = 'Feijoada!';
print(myMeal.description);// 修正代码问题后,打印:Meal description: Feijoada!
解决上述代码中存在的问题,只需将第1行代码修正为:
late String _description;
late
关键字对于复杂的模式(如循环引用)很有帮助。下面的代码有两个对象,它们需要维护对彼此的不可为null
的引用。
You can create late final variables: you set their values once, and after that they’re read-only.
注:对于
final
修饰的变量,添加late
关键字,不需要移除final
,可以创建late final variables
:一旦设置值,之后就是‘只读’了。
class Team
late final Coach coach;
class Coach
late final Team team;
void main()
final myTeam = Team();
final myCoach = Coach();
myTeam.coach = myCoach;
myCoach.team = myTeam;
print('All done!');
若Team类和Coach类的实例变量任一个不加late
,则错误提示:The final variable ‘coach’ / ‘team’ must be initialized.
这里有另一种模式,late
可以提供帮助:对昂贵的非空字段进行延迟初始化。试试这个:
- 运行此代码而不进行更改,并记录输出。
- 将_cache设为
late
字段,然后运行代码。输出是一样的吗?
int _computeValue()
print('In _computeValue...');
return 3;
class CachedValueProvider
final _cache = _computeValue();
int get value => _cache;
void main()
var provider = CachedValueProvider();
print('Getting value...');
print('The value is $provider.value!');
输出:
In _computeValue...
Getting value...
The value is 3!
将_cache设为late
字段,输出:
Getting value...
In _computeValue...
The value is 3!
有趣的事实:在你将late
添加到_cache声明之后,如果你将_computeValue函数移动到CachedValueProvider类中,代码仍然有效!后期字段的初始化表达式可以在其初始值设定项中使用实例方法。
五、健全的空安全(sound null safety)
Dart 语言已支持健全的空安全机制!
当你选择使用空安全时,代码中的类型将默认是非空的,意味着除非你声明它们可空,它们的值都不能为空。有了空安全,原本处于你的运行时的空值引用错误将变为 编辑时 的分析错误。
有了空安全,下面代码中所有的变量都是非空的:
// In null-safe Dart, none of these can ever be null.
var i = 42; // Inferred to be an int.
String name = getFileName();
final b = Foo();
若你想让变量可以为 null
,只需要在类型声明后加上 ?
。
int? aNullableInt = null;
你可以在你的普通开发环境中使用空安全,也建议迁移你项目中的代码至空安全。
空安全的原则
Dart 的空安全支持基于以下三条核心原则:
- 默认不可空。除非你将变量显式声明为可空,否则它一定是非空的类型。我们在研究后发现,非空是目前的 API 中最常见的选择,所以选择了非空作为默认值。
- 渐进迁移。你可以自由地选择何时进行迁移,多少代码会进行迁移。你可以使用混合模式的空安全,在一个项目中同时使用空安全和非空安全的代码。我们也提供了帮助你进行迁移的工具。
- 完全可靠。Dart 的空安全是非常可靠的,意味着编译期间包含了很多优化。如果类型系统推断出某个变量不为空,那么它永远不为空。当你将整个项目和其依赖完全迁移至空安全后,你会享有健全性带来的所有优势——更少的 BUG、更小的二进制文件以及更快的执行速度。
启用空安全
健全的空安全已在 Dart 2.12 和 Flutter 2 中可用。Dart 2.12 或更高的版本中Dart 和 Flutter 的核心库现已完全迁移至空安全,而尚未进行迁移的应用仍然可以使用。
更早期的版本可按需迁移至空安全。
以上是关于▩Dart-空安全(Null Safety)的主要内容,如果未能解决你的问题,请参考以下文章