聊聊 Dart 的空安全 (null safety) 特性
前言
关于 null safety
其实并不是什么新鲜事了,很早的时候 Swift 就已经支持了,Dart 是从 2.12.2 版本开始支持该特性的。本篇以官方文档为蓝本,聊一下 Dart 的 null safety
特性。官方文档链接:Null Safety。
null safety
最大的特点就是默认声明的对象是非空的,除非你明确该对象可以为空。
Nullable 和 non-nullable 类型
当你选择使用 null satety
特性时,所有的类型默认是非空的。例如如果声明了一个 String
类型的变量,那么就意味着它一直包含字符串值。如果你想要一个 String
对象能够接收字符串值或null
,那么就需要在类型声明后面加上?
标识,一个声明为String?
类型的变量可以包含字符串值或 null
。
空断言操作符!
如果确定一个对象或表达式返回值有值,那么就可以使用空断言操作符!强制转为不为空对象,然后可以使用它赋值给非空对象,或访问其属性或方法,如 valiable!.xx
。这种情况下,如果对于nullable
对象不加!,编译器就会报错。但是,如果对象本身是null
,加!
操作符会导致异常。
类型提升(Type promotion)
为了保证空安全特性,Dart 的流分析(flow analysis)已经考虑了空特性。如果一个 nullable 对象不可能有空值,那么就会被当作非空对象处理,例如:
late 关键字
有些时候变量、类成员属性或其他全局变量应该是非空的,但是没法在声明的时候直接赋值,这个时候就需要在变量声明的时候加上 late
关键字。当在变量声明的时候加上late
关键字后,就是告诉 Dart 如下的内容:
目前还没有给该变量赋值;
我们将在之后才给该变量赋值;
我们保证在使用该变量前肯定会对其赋值。
例如,下面的_description 声明编译器如果没有 late 关键字会报错。
late
关键字对处理循环引用还十分有帮助,譬如我们有一个球队和一个教练,球队和教练就存在相互应用的情况。如果没有 late
关键字,我们就只能声明为 nullable,那样到时候使用到时候就很别扭了——需要到处加空断言操作符或者使用if
来判断是否为空。
升级修改
升级修改时,需要根据调用的方法参数、返回值或声明的属性做如下处理:
类属性:非空类属性默认需要由初始值,如果类属性会在别的方法中初始化,那可以加上
late
关键字,表示该属性稍后会被初始化,而且是非空的。如果属性可能为空,那么就加上?
空标识。这种可为空的属性使用的时候需要特别注意,需要检查是否为空才可以使用,或者使用variable?.xx
这种形式访问,如果明确属性有值,则需要使用!强制指定为非空,如variable!.xx
。方法参数:根据需要设置参数是否是可为空或必传参数,必传的参数加上在参数声明前加上
required
关键字,可为空的加上?
标识。返回值:如果返回值可能为
null
,就在返回参数后加上?
标识。如果是集合对象中的某个对象为空,那么需要在集合的类型后加上?标识,例如List<int?>
。
对于依赖,也需要修改 pubspec.yaml 文件,包括如下修改:
将依赖最低的 Dart 版本修改为 2.12.0
修改部分第三方插件依赖,升级到支持 null safety 版本,具体可以参考 pub 上的版本说明。
Dio 踩坑
升级完之后,Dio
请求报错DioError [DioErrorType.other]: type 'Null' is not a subtype of type 'Object'
。上网搜了,在 issue
里有提到过,但是说是已经解决了。然后按照 issue 里的方法试也不行,后面想了一下,先直接请求百度网页看看是不是 Dio
的问题,结果请求百度网页正常,那就说明是代码自身的问题。最后再定位发现 是我们的 CookieManager
拦截器的请求 headers
设置 Cookie
字段的时候,当_cookie
为 null
的时候导致出现空异常了。这时候我们要检查一下,如果_cookie
不为空才设置 Cookie
。
总结
从编码的角度来说,null safety
特性实际上增加了编码的工作量。但是null safety
更像是一个强制的约定,要求接口或类明确参数或属性的是否为空,从而可以简化协作,提高代码的健壮性。
当然,对于第三方库来说就需要特别小心,有些第三方库使用的是 dynamic 声明的场合,目前 Dart
对 dynamic
声明的变量、属性是不做空校验的,这会导致这样声明的出现空异常,例如上面说到的 RequestOptions options
的 headers
,就是一个 Map<String, dynamic>
对象,结果使用 null
赋值的时候就会抛出异常。对于这种情况最好是尽量少用 dynamic
声明,同时调用第三方的时候,如果发现有这种情况,需要检查一下是否允许赋值 null
。
版权声明: 本文为 InfoQ 作者【岛上码农】的原创文章。
原文链接:【http://xie.infoq.cn/article/ef68f641631024c7188001d47】。文章转载请联系作者。
评论