Dart中的空安全是什么?

104

我听说了新的Dart空安全语言特性(NNBD),目前是“'非空' 实验”。它应该默认引入非空

该功能规范可以在这里找到,语言GitHub问题在此处

它是如何工作的,我在哪里可以尝试它?


7
我认为 Dart 团队有些过了头,就像 Java 强制我们捕获所有异常一样。代码变得非常嘈杂。 - s k
第一个链接已损坏(404)。 - Peter Mortensen
2个回答

264

1. Null安全性 / 非空(默认情况下)

Null安全性/非空(默认情况下),简称NNBD,目前可以在nullsafety.dartpad.dev找到。

请注意,您可以阅读此处的完整规范此处的完整路线图。现在,Dart也已经官方宣布了声音的null安全性。点击这里查看


2.1. 默认情况下的非空是什么意思?

void main() {
  String word;
  print(word); // illegal

  word = 'Hello, ';
  print(word); // legal
}

如上所示,默认情况下非空(non-nullable)的变量意味着每个通常声明的变量不能null。因此,在变量被赋值之前访问该变量的任何操作都是不合法的。
此外,将 null 赋值给非空变量也是不允许的:

void main() {
  String word;
  
  word = null; // forbidden
  world = 'World!'; // allowed
}

2.1.1. 这对我有什么帮助?

如果一个变量是 非空 的,你可以确定它永远不会是 null。因此,你无需事先检查它。

int number = 4;

void main() {
  if (number == null) return; // redundant

  int sum = number + 2; // allowed because number is also non-nullable
}

2.1.2. Remember

如果类中的实例字段是非空字段,必须初始化

class Foo {
  String word; // forbidden

  String sentence = 'Hello, World!'; // allowed
}

请看下面的 late 来修改这种行为。

2.2. 可空类型 (?)

您可以在变量类型后添加问号 ? 来使用可空类型

class Foo {
  String word; // forbidden

  String? sentence; // allowed
}

可空变量在使用之前无需初始化,它会默认初始化为null

void main() {
  String? word;
  
  print(word); // prints null
}

2.2.2. !

!附加到任何变量e上,如果e为null,则会抛出运行时错误,否则将其转换为非空值v

void main() {
  int? e = 5;
  int v = e!; // v is non-nullable; would throw an error if e were null

  String? word;
  print(word!); // throws runtime error if word is null

  print(null!); // throws runtime error
}

2.3. late

late关键字可以用来标记那些在声明时不进行初始化,而是在被访问时才进行初始化的变量,即延迟初始化。这也意味着我们可以拥有非空的实例字段,并在稍后进行初始化:

class ExampleState extends State {
  late final String word; // non-nullable

  @override
  void initState() {
    super.initState();

    // print(word) here would throw a runtime error
    word = 'Hello';
  }
}

在初始化之前访问word会抛出运行时错误。

2.3.1. late final

现在,final变量也可以标记为late:

late final int x = heavyComputation();

只有当访问 x 时才会调用heavyComputation。此外,您还可以声明一个没有初始化器的 late final,这与仅有一个late变量相同,但是它只能分配一次。

late final int x;
// w/e
x = 5; // allowed
x = 6; // forbidden

请注意,所有带有初始值的顶层静态变量现在都将被评估为late,无论它们是否是final

2.4. required

之前是一个注释@required),现在作为修饰符内置。它允许将任何命名参数(用于函数或类)标记为required,这使它们不可为空:

void allowed({required String word}) => null;

这也意味着,如果参数应该是不可为空的,它需要被标记为required或具有默认值:

void allowed({String word = 'World'}) => null;

void forbidden({int x}) // compile-time error because x can be null (unassigned)
    =>
    null;

任何其他的命名参数都必须可为空

void baz({int? x}) => null;

2.5. ?[]

针对索引操作符[]添加了空安全的?[]运算符:

void main() {
  List<int>? list = [1, 2, 3];

  int? x = list?[0]; // 1
}

另请参阅有关语法决策的文章

2.5.1. ?..

级联运算符现在也有了一个新的空安全运算符:?..。如果接收方不为null,它会导致以下级联操作只执行一次。因此?..必须是级联序列中的第一个级联运算符:

void main() {
  Path? path;

  // Will not do anything if path is null.
  path
    ?..moveTo(3, 4)
    ..lineTo(4, 3);

  // This is a noop.
  (null as List)
    ?..add(4)
    ..add(2)
    ..add(0);
}

2.6. Never

下面的解释很差。请阅读“了解空安全性”中的“顶部和底部”以获取更好的解释。

为避免混淆:这不是开发人员需要担心的事情。我提及它是为了完整性的缘故。

Never 将成为像先前存在的 Null(而不是 null)一样的类型,定义在 dart:core 中。这两个类都不能被扩展、实现或混合,因此不打算使用它们。

本质上,Never 意味着不允许使用任何类型,且 Never 本身不能被实例化。
一个 List<Never> 中除了 Never 之外任何其他东西都无法满足列表的泛型约束,这意味着它必须为空。List<Null> 可以包含 null

// Only valid state: []
final neverList = <Never>[
  // Any value but Never here will be an error.
  5, // error
  null, // error

  Never, // not a value (compile-time error)
];

// Can contain null: [null]
final nullList = <Null>[
  // Any value but Null will be an error.
  5, // error
  null, // allowed

  Never, // not a value (compile-time error)
  Null, // not a value (compile-time error)
];

示例:编译器将为一个const List<T>推断出List<Never>
就我所知,Never不应该被程序员使用。(我错了)。

3. 了解更多

您可以阅读关于空安全的官方文章
此外,如开头所述,您可以在DartPad上试试


13
可以举出哪些情况下可以使用“Never”这个词语? - Ramses Aldama
2
我们决定使用"?[]"作为空安全索引运算符,而不是"?.[]"。后者在语法上稍微复杂一些,但这正是用户想要的。 - munificent
1
应该指出,对于成员变量或实例变量的“late final”修饰符只在运行时进行检查。由于停机问题,在开发时间或编译时间无法检查它。因此,您将无法获得IDE的帮助。 - Graham
1
有没有一种方法可以默认使用非空类型来编译代码,或者Dartpad是唯一可以尝试这种方式的地方? - nbloqs
@RamsesAldama 永远没有值,也永远无法成功执行。 - Felipe Sales
显示剩余7条评论

-2
如果您想要此字段为必填项,则使用“required”关键字。否则,只需添加“?”即可。就像这样:

const phonefield({
    Key? key, required this.onchanged,
}) : super(key: key);

final  ValueChanged<String>onchanged;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接