前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Dart空安全终极指南

Dart空安全终极指南

作者头像
用户1974410
发布2022-09-20 16:51:27
1.1K0
发布2022-09-20 16:51:27
举报
文章被收录于专栏:flutter开发精选

Dart中的空安全

Null Safety 的引入是 Dart 语言的一个重要里程碑。Null Safety 通过「在开发期间而不是在运行时捕获 null 错误来」帮助您避免一整类问题。

本文将通过一些例子来展示如何使用新的 Null Safety 功能。

❝Null Safety 可作为 Flutter 2.0 的稳定版本使用,并且默认为所有使用 Flutter 2.2 创建的项目启用。 ❞

Dart的类型系统

Dart 有一个「健全的类型系统」。当我们编写 Dart 代码时,「类型检查器」会确保我们不能编写下面这样的代码:

代码语言:javascript
复制
int age = "hello world"; // A value of type `String` can't be assigned to a variable of type `int`

此代码产生一个错误,告诉我们*“String不能将值分配给类型为变量的变量int”*。

同样,当我们在 Dart 中编写函数时,可以指定返回「类型」

代码语言:javascript
复制
int square(int value) {
  return value * value;
}

由于「类型安全」,Dart 可以 100% 保证这个函数「总是」返回一个int.

但是类型安全本身并不能保证变量(或返回值)不是null.

所以下面的代码编译正常,但「在运行时」生成异常:

代码语言:javascript
复制
square(null);
// Unhandled Exception: NoSuchMethodError: The method '*' was called on null.

在这个例子中,很容易发现问题。但在大型项目中,很难跟踪什么可以和不可以是null

要判断是否为null 可能要添加很多额外的代码:

代码语言:javascript
复制
int square(int value) {
  assert(value != null); // for debugging
  if (value == null) throw Exception();
  return value * value;
}

现在,我们有了 一个更好的解决方案。

Dart Null Safety

Dart 2.12默认启用空安全,启用空安全会有如下3个好处:

  • 我们可以编写「编译时」保证的强壮的空安全代码。可以让我们更有效率,因为 Dart 可以告诉我们什么时候做错了。
  • 可以更容易地声明我们的**意图,**这样的API 更易于使用。
  • Dart 编译器可以优化我们的代码,从而生成更小更快的程序。

下面,我们去看看 Null Safety 是如何工作的。

声明不可为空的变量

主要的变化是现在所有类型「默认情况下」都是不可为空。

代码语言:javascript
复制
void main() {
  int age; // non-nullable
  age = null; // A value of type `Null` can't be assigned to a variable of type 'int'
}

这段代码是无法编译的。

当使用不可为空的变量时,我们必须遵守一个重要的原则:

❝不可为空的变量必须始终使用非空值进行初始化。 ❞

牢记这个原创,我们对新语法就更容易理解了。

我们再看下上面那个例子:

代码语言:javascript
复制
int square(int value) {
  return value * value;
}

value和返回值都保证不是null,因此 当我们在传null值时编译期间就会报错。

代码语言:javascript
复制
square(null);
// The argument type 'Null' can't be assigned to the parameter type 'int'

如果有时候我们就像声明可为空的变量,要怎么办呢?

声明可为空变量

声明可为空变量需要使用 标识符

代码语言:javascript
复制
String? name;  // initialized to null by default
int? age = 36;  // initialized to non-null
age = null; // can be re-assigned to null

❝注意:不需要在使用之前初始化可为空的变量。默认初始化为null。 ❞

以下是声明可空变量的其他方法:

代码语言:javascript
复制
// nullable function argument
void openSocket(int? port) {
  // port can be null
}

// nullable return type
String? lastName(String fullName) {
final components = fullName.split(' ');
  return components.length > 1 ? components.last : null;
}

// using generics
T? firstNonNull<T>(List<T?> items) {
  // returns first non null element in list if any
  return items.firstWhere((item) => item != null);
}

断言运算符

但是在某些情况下,我们知道某个变量不可能null,但我们无法向编译器「证明」它。在这种情况下,可以使用断言运算符。

代码语言:javascript
复制
int? maybeValue = 42;
int value = maybeValue!; // valid, value is non-nullable

这样,我们相当于告诉Dart,maybeValue是非空的,可以将它赋值给非空变量value。

请注意,将断言运算符应用于null值将引发运行时异常:

代码语言:javascript
复制
String? name;
print(name!); // NoSuchMethodError: '<Unexpected Null Value>'
print(null!); // NoSuchMethodError: '<Unexpected Null Value>'

所以,当断言错误时,! 将引发运行时异常。

有时我们需要使用返回可空值的函数:

代码语言:javascript
复制
String? lastName(String fullName) {
  final components = fullName.split(' ');
  return components.length > 1 ? components.last : null;
}

所以返回值需要赋值给一个非空变量:

代码语言:javascript
复制
// prefer this:
String last = lastName('Andrea Bizzotto')!;
// to this:
String? last = lastName('Andrea Bizzotto');

总结一下:

  • 尽量使用不可为空的变量,这样就会将很多null错误拦截在「编译时」
  • 如果知道可以为空的表达式不会是null,则可以使用运算符将其分配给不可为空的变量。

非空和空的一些使用技巧

在dart中我们一定要添加对null的检查,这样我们的代码才能更健壮。

代码语言:javascript
复制
int absoluteValue(int? value) {
  if (value == null) {
    return 0;
  }
  // if we reach this point, value is non-null
  return value.abs();
}

这样,我们在返回值时就可以使用value.abs() ,不用使用value?.abs()

我们再看看如下的代码

代码语言:javascript
复制
int sign(int x) {
  int result; // non-nullable
  print(result.abs()); // invalid: 'result' must be assigned before it can be used
  if (x >= 0) {
    result = 1;
  } else {
    result = -1;
  }
  print(result.abs()); // ok now
  return result;
}

在result被初始化前使用就会报错,result在被使用前被赋值为非空,dart就不会报错。

在类中使用不可为空变量

如果类中的实例变量不可为空,则必须对其进行初始化:

代码语言:javascript
复制
class BaseUrl {
  String hostName; // Non-nullable instance field 'hostName' must be initialized

  int port = 80; // ok
}

如果不能使用默认值初始化,则可以使用构造函数对其进行设置:

代码语言:javascript
复制
class BaseUrl {
  BaseUrl(this.hostName);
  String hostName; // now valid

  int port = 80; // ok
}

不可为空的命名和位置参数

使用 Null Safety,必须始终「要求传递」不可为空的「命名」参数或设置「默认值」

一般函数以及类构造函数,都遵循以上原则:

代码语言:javascript
复制
void printAbs({int value}) {  // 'value' can't have a value of null because of its type, and no non-null default value is provided
  print(value.abs());
}

class Host {
  Host({this.hostName}); // 'hostName' can't have a value of null because of its type, and no non-null default value is provided
  final String hostName;
}

我们可以使用新的修饰符required来修复上面的代码:

代码语言:javascript
复制
void printAbs({required int value}) {
  print(value.abs());
}

class Host {
  Host({required this.hostName});
  final String hostName;
}

这样我们在使用时,编译器可以轻松识别出一些低级错误:

代码语言:javascript
复制
printAbs(); // The named parameter 'value' is required, but there's no corresponding argument
printAbs(value: null); // The argument type 'Null' can't be assigned to the parameter type 'int'
printAbs(value: -5); // ok

final host1 = Host(); // The named parameter 'hostName' is required, but there's no corresponding argument
final host2 = Host(hostName: null); // The argument type 'Null' can't be assigned to the parameter type 'String'
final host3 = Host(hostName: "example.com"); //

如果使用可为空的变量,则reqired可以省略:

代码语言:javascript
复制
class Host {
  Host({this.hostName});
  final String? hostName; // nullable, initialized to `null` by default
}
// all valid cases
final host1 = Host(); // hostName is null
final host2 = Host(hostName: null); // hostName is null
final host3 = Host(hostName: "example.com");

「位置」参数遵循相同的规则:

代码语言:javascript
复制
class Host {
  Host(this.hostName); // ok
  final String hostName;
}

class Host {
  Host([this.hostName]); // The parameter 'hostName' can't have a value of 'null' because of its type, and no non-null default value is provided
  final String hostName;
}

class Host {
  Host([this.hostName = "www.codewithandrea.com"]); // ok
  final String hostName;
}

class Host {
  Host([this.hostName]); // ok
  final String? hostName;
}

最后,我们必须牢记一个黄金原则就不会出错:

❝不可为空的变量必须始终使用非空值进行初始化。 ❞

空感知级联运算符

为了处理 Null Safety,级联运算符现在有一个新的 变体:?... 例子:

代码语言:javascript
复制
Path? path;
// will not do anything if path is null
path
  ?..moveTo(0, 0)
  ..lineTo(0, 2)
  ..lineTo(2, 2)
  ..lineTo(2, 0)
  ..lineTo(0, 0);

上面的级联操作只有在path 不为空时才会执行。

空感知下表运算符

之前的用法:

代码语言:javascript
复制
int? first(List<int>? items) {
  return items != null ? items[0] : null; // null check to prevent runtime null errors
}

Dart 2.9后可以使用如下

代码语言:javascript
复制
int? first(List<int>? items) {
  return items?[0]; 
}

late关键词

late关键字在「第一次读取」时初始化变量,而不是在「创建」时初始化。看下面的例子:

代码语言:javascript
复制
class ExampleState extends State {
  late final TextEditingController textEditingController;

  @override
  void initState() {
    super.initState();
    textEditingController = TextEditingController();
  }
}

现在我们可以简化成如下:

代码语言:javascript
复制
class ExampleState extends State {
  // late - will be initialized when first used (in the build method)
  late final textEditingController = TextEditingController();
}

通常latefinal,结合使用,将「只读」变量的创建「推迟」到首次读取时。

当初始化时会做很多任务时,这样做很理想:

代码语言:javascript
复制
late final taskResult = doHeavyComputation();

在函数体内使用时:

代码语言:javascript
复制
void foo() {
  late final int x;

  x = 5; // ok
  x = 6; // The late final local variable is already definitely initialized
}

不太建议以这种方式使用late变量。因为这样会导致不明显的运行时错误。例子:

代码语言:javascript
复制
class X {
  late final int x;
  
  void set1() => x = 1;
  
  void set2() => x = 2;
}

void main() {
  X x = X();
  x.set1();
  print(x.x);
  x.set2(); // LateInitializationError: Field 'x' has already been initialized.
  print(x.x);
}

但我建议对late谨慎使用,并始终在用late声明变量时对其进行初始化。

静态和全局变量

除了late,所有全局变量「必须在声明时初始化,」

代码语言:javascript
复制
int global1 = 42; // ok

int global2; // The non-nullable variable 'global2' must be initialized

late int global3; // ok

静态类变量也是同样的:

代码语言:javascript
复制
class Constants {
  static int x = 10; // ok
  static int y; // The non-nullable variable 'y' must be initialized
  static late int z; // ok
}

最后

Null Safety 是 Dart 语言的一个重大变化,它可以帮助你编写更好、更安全的代码,「只要你正确使用它」

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-01-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 flutter开发精选 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Dart中的空安全
  • Dart的类型系统
  • Dart Null Safety
  • 声明不可为空的变量
  • 声明可为空变量
  • 断言运算符
  • 非空和空的一些使用技巧
  • 在类中使用不可为空变量
  • 不可为空的命名和位置参数
  • 空感知级联运算符
  • 空感知下表运算符
  • late关键词
  • 静态和全局变量
  • 最后
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档