▩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可以提供帮助:对昂贵的非空字段进行延迟初始化。试试这个:

  1. 运行此代码而不进行更改,并记录输出。
  2. 将_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)的主要内容,如果未能解决你的问题,请参考以下文章

迁移空安全后颤振模拟单元测试错误

如何在 Flutter 中启用 Null-Safety?

Flutter 升级空安全攻略

如何实际检查 null 以符合 Dart 中的 Sound null-safety [重复]

使用 if 检查进行 Dart 空安全 [重复]

即构音视频 Express Flutter SDK 全面支持空安全