问题描述
我已将我的 Dart 代码迁移到 NNBD / Null Safety。其中一些看起来像这样:
class Foo {
String? _a;
void foo() {
if (_a != null) {
_a += 'a';
}
}
}
class Bar {
Bar() {
_a = 'a';
}
String _a;
}
这会导致两个分析错误。对于_a += 'a';
:
值可以为“null”的表达式必须先进行空值检查,然后才能取消引用。 尝试在取消引用之前检查该值是否为“空”。
对于Bar() {
:
不可为空的实例字段 '_a' 必须被初始化。 尝试添加初始化表达式,或在此构造函数中添加字段初始化器,或将其标记为“迟到”。
我使用的是 Dart 2.12.0-133.2.beta(12 月 15 日星期二)。
编辑:我发现 this page 说:
分析器无法对整个应用程序的流程进行建模,因此无法预测全局变量或类字段的值。
但这对我来说没有意义 - 在这种情况下,从 if (_a != null)
到 _a += 'a';
只有一个可能的流控制路径 - 没有异步代码并且 Dart 是单线程的 - 所以它没有_a
不是本地的也没关系。
Bar()
的错误消息明确说明了在构造函数中初始化字段的可能性。
解决方法
问题是即使类字段被标记为final
,也可以被覆盖。下面的例子说明了这个问题:
class A {
final String? text = 'hello';
String? getText() {
if (text != null) {
return text;
} else {
return 'WAS NULL!';
}
}
}
class B extends A {
bool first = true;
@override
String? get text {
if (first) {
first = false;
return 'world';
} else {
return null;
}
}
}
void main() {
print(A().getText()); // hello
print(B().getText()); // null
}
B
类覆盖 text
最终字段,因此它在第一次被询问时返回一个值,但在此之后返回 null
。编写 A
类时不能阻止这种形式的覆盖被允许。
因此我们不能将 getText
的返回值从 String?
更改为 String
即使看起来我们在返回之前检查了 text
字段的 null
值可以为“空”的表达式必须先进行空检查,然后才能取消引用。尝试在取消引用之前检查该值是否为“空”。
这似乎真的只适用于局部变量。此代码没有错误:
class Bar {
Bar() : _a = 'a';
String _a;
}
虽然有点烂。我的代码现在充满了将类成员复制到局部变量然后再复制回来的代码。 :-/
不可为空的实例字段 '_a' 必须被初始化。尝试添加初始化表达式,或在此构造函数中添加字段初始化器,或将其标记为“延迟”。
啊,原来“字段初始值设定项”实际上是这样的:
{{1}},
处理这种情况的方法很少。我已经给出了一个 detailed answer here,所以我只是从中编写解决方案:
-
使用局部变量(推荐)
void foo() { var a = this.a; // <-- Local variable if (a != null) { a += 'a'; this.a = a; } }
-
使用 ??
void foo() { var a = (this.a ?? '') + 'a'; this.a = a; }
-
使用 Bang 运算符 (!)
仅当您 100% 确定变量 (
a
) 在使用时不是null
时,才应使用此解决方案。void foo() { a = a! + 'a'; // <-- Bang operator }
回答你的第二个问题:
不可为空的字段应该总是被初始化。初始化的方式一般有以下三种:
-
在声明中:
class Bar { String a = 'a'; }
-
在初始化形式
class Bar { String a; Bar({required this.a}); }
-
在初始化列表中:
class Bar { String a; Bar(String b) : a = b; }
你可以像这样在 null-safety 中创建你的类
class JobDoc {
File? docCam1;
File? docCam2;
File? docBarcode;
File? docSignature;
JobDoc({this.docCam1,this.docCam2,this.docBarcode,this.docSignature});
JobDoc.fromJson(Map<String,dynamic> json) {
docCam1 = json['docCam1'] ?? null;
docCam2 = json['docCam2'] ?? null;
docBarcode = json['docBarcode'] ?? null;
docSignature = json['docSignature'] ?? null;
}
}