Skip to content

下面是创建并初始化变量的示例:

dart
var name = 'Bob';

变量存储的是引用。名为 name 的变量包含一个对值为 "Bob" 的 String 对象的引用。

name 变量的类型被推断为 String,但你可以通过显式指定类型来改变它。如果一个对象不限于单一类型,可以指定 Object 类型(必要时使用 dynamic)。

dart
Object name = 'Bob';

另一种选择是显式声明本会被推断出的类型:

dart
String name = 'Bob';

注意
本页面遵循风格指南的建议,对局部变量使用 var 而非类型注解。

空安全

Dart 语言强制实施健全的空安全特性。

空安全可防止因意外访问被设为 null 的变量而导致的错误,这种错误被称为空引用错误。当你访问一个求值为 null 的表达式的属性或调用其方法时,就会发生空引用错误。不过,当 null 支持该属性或方法(如 toString()hashCode)时,则为例外。借助空安全,Dart 编译器会在编译时检测到这些潜在错误。

例如,假设你想求一个 int 变量 i 的绝对值。如果 inull,调用 i.abs() 会导致空引用错误。在其他语言中,这样做可能会导致运行时错误,但 Dart 编译器会禁止此类操作。因此,Dart 应用不会引发运行时空引用错误。

空安全引入了三个关键变化:

  1. 当你为变量、参数或其他相关组件指定类型时,可以控制该类型是否允许为 null。若要启用可空性,可在类型声明末尾添加 ?

    dart
    String? name  // 可空类型。可以是 `null` 或字符串。
    
    String name   // 非空类型。不能是 `null`,但可以是字符串。
  2. 必须在使用变量之前对其进行初始化。可空变量默认值为 null,因此它们会被自动初始化。Dart 不会为非空类型设置初始值,而是强制你设置初始值。Dart 不允许你使用未初始化的变量,这可防止你在接收者类型可能为 nullnull 不支持所使用的方法或属性时,访问其属性或调用其方法。

  3. 不能对具有可空类型的表达式访问属性或调用方法。不过,当该属性或方法为 null 所支持(如 hashCodetoString())时,则为例外。

健全的空安全将潜在的运行时错误转变为编辑时分析错误。当出现以下情况时,空安全会标记非空变量:

  • 未使用非空值进行初始化。
  • 被赋值为 null

这种检查让你能够在部署应用之前修复这些错误。

默认值

具有可空类型且未初始化的变量,其初始值为 null。即使是数值类型的变量,初始值也是 null,因为在 Dart 中,数字与其他所有事物一样,都是对象。

dart
int? lineCount;
assert(lineCount == null);

注意
生产环境代码会忽略 assert() 调用。而在开发期间,若 assert(condition) 中的条件为 false,则会抛出异常。详情请查阅 Assert

启用空安全后,必须在使用非空变量之前对其值进行初始化:

dart
int lineCount = 0;

你不必在声明局部变量时就对其进行初始化,但必须在使用它之前为其赋值。例如,以下代码是有效的,因为 Dart 能够检测到在将 lineCount 传递给 print() 时,它是非空的:

dart
int lineCount;

if (weLikeToCount) {
  lineCount = countLines();
} else {
  lineCount = 0;
}

print(lineCount);

顶级变量和类变量是延迟初始化的,初始化代码会在变量首次被使用时运行。

延迟变量

late 修饰符有两种使用场景:

  1. 声明一个在声明后才被初始化的非空变量。
  2. 延迟初始化变量。

通常,Dart 的控制流分析能够检测到非空变量在使用前是否已被赋值为非空值,但有时分析会失败。两种常见的情况是顶级变量和实例变量:Dart 通常无法确定它们是否已被设置,因此不会尝试进行检测。

如果你确定某个变量在使用前已被设置,但 Dart 不认同,你可以通过将该变量标记为 late 来修复此错误:

dart
late String description;

void main() {
  description = 'Feijoada!';
  print(description);
}

注意
如果你未能初始化一个 late 变量,那么在使用该变量时会发生运行时错误。

当你将一个变量标记为 late 但在声明时就对其进行初始化时,初始化器会在变量首次被使用时运行。这种延迟初始化在以下几种情况下很有用:

  • 变量可能不会被使用,并且初始化它的成本很高。
  • 你正在初始化一个实例变量,并且其初始化器需要访问 this

在以下示例中,如果从未使用 temperature 变量,那么昂贵的 readThermometer() 函数将永远不会被调用:

dart
// 这是程序对 readThermometer() 的唯一调用。
late String temperature = readThermometer(); // 延迟初始化。

Final 和 Const

如果你不打算改变一个变量,可使用 finalconst,既可以替代 var,也可以与类型一起使用。final 变量只能设置一次;const 变量是编译时常量(const 变量隐式为 final)。

注意
实例变量可以是 final,但不能是 const

以下是创建和设置 final 变量的示例:

dart
final name = 'Bob'; // 没有类型注解
final String nickname = 'Bobby';

你不能更改 final 变量的值:

dart
✗ 静态分析:失败
name = 'Alice'; // 错误:final 变量只能设置一次。

对需要是编译时常量的变量使用 const。如果 const 变量在类级别,需将其标记为 static const。在声明变量时,将其值设置为编译时常量,如数字或字符串字面量、const 变量,或常量数字的算术运算结果:

dart
const bar = 1000000; // 压力单位(达因/平方厘米)
const double atm = 1.01325 * bar; // 标准大气压

const 关键字不仅用于声明常量变量。你还可以用它来创建常量值,以及声明创建常量值的构造函数。任何变量都可以有常量值。

dart
var foo = const [];
final bar = const [];
const baz = []; // 相当于 `const []`

你可以在 const 声明的初始化表达式中省略 const,如上面对 baz 的处理。详情见 不要冗余使用 const

你可以更改非 final、非 const 变量的引用,即使它曾经有一个 const 值:

dart
foo = [1, 2, 3]; // 原来是 const []

你不能更改 const 变量的值:

dart
✗ 静态分析:失败
baz = [42]; // 错误:常量变量不能被赋值。

你可以定义使用类型检查和类型转换(isas)、集合 if 和展开运算符(......?)的常量:

dart
const Object i = 3; // i 是一个具有 int 值的 const Object...
const list = [i as int]; // 使用类型转换。
const map = {if (i is int) i: 'int'}; // 使用 is 和集合 if。
const set = {if (list is List<int>) ...list}; // ...和展开运算符。

注意
虽然 final 对象不能被修改,但其字段可以更改。相比之下,const 对象及其字段不能被更改:它们是不可变的。

有关使用 const 创建常量值的更多信息,请参阅 列表、映射和类

通配符变量

版本说明
通配符变量需要至少 3.7 的语言版本。

名为 _ 的通配符变量声明一个非绑定的局部变量或参数;本质上是一个占位符。如果有初始化器,它仍然会被执行,但该值无法访问。在同一命名空间中可以存在多个名为 _ 的声明而不会发生冲突错误。

可能影响库隐私的顶级声明或成员不是通配符变量的有效用法。块作用域内的局部声明(如下例)可以声明通配符:

  1. 局部变量声明:

    dart
    main() {
      var _ = 1;
      int _ = 2;
    }
  2. For 循环变量声明:

    dart
    for (var _ in list) {}
  3. Catch 子句参数:

    dart
    try {
      throw '!';
    } catch (_) {
      print('oops');
    }
  4. 泛型类型和函数类型参数:

    dart
    class T<_> {}
    void genericFunction<_>() {}
    
    takeGenericCallback(<_>() => true);
  5. 函数参数:

    dart
    Foo(_, this._, super._, void _()) {}
    
    list.where((_) => true);
    
    void f(void g(int _, bool _)) {}
    
    typedef T = void Function(String _, String _);

提示
启用 unnecessary_underscores lint 规则,以识别单个非绑定通配符变量 _ 何时可以替换以前使用多个绑定下划线(_____ 等)以避免名称冲突的约定。