下面是创建并初始化变量的示例:
var name = 'Bob';变量存储的是引用。名为 name 的变量包含一个对值为 "Bob" 的 String 对象的引用。
name 变量的类型被推断为 String,但你可以通过显式指定类型来改变它。如果一个对象不限于单一类型,可以指定 Object 类型(必要时使用 dynamic)。
Object name = 'Bob';另一种选择是显式声明本会被推断出的类型:
String name = 'Bob';注意
本页面遵循风格指南的建议,对局部变量使用 var 而非类型注解。
空安全
Dart 语言强制实施健全的空安全特性。
空安全可防止因意外访问被设为 null 的变量而导致的错误,这种错误被称为空引用错误。当你访问一个求值为 null 的表达式的属性或调用其方法时,就会发生空引用错误。不过,当 null 支持该属性或方法(如 toString() 或 hashCode)时,则为例外。借助空安全,Dart 编译器会在编译时检测到这些潜在错误。
例如,假设你想求一个 int 变量 i 的绝对值。如果 i 为 null,调用 i.abs() 会导致空引用错误。在其他语言中,这样做可能会导致运行时错误,但 Dart 编译器会禁止此类操作。因此,Dart 应用不会引发运行时空引用错误。
空安全引入了三个关键变化:
当你为变量、参数或其他相关组件指定类型时,可以控制该类型是否允许为
null。若要启用可空性,可在类型声明末尾添加?。dartString? name // 可空类型。可以是 `null` 或字符串。 String name // 非空类型。不能是 `null`,但可以是字符串。必须在使用变量之前对其进行初始化。可空变量默认值为
null,因此它们会被自动初始化。Dart 不会为非空类型设置初始值,而是强制你设置初始值。Dart 不允许你使用未初始化的变量,这可防止你在接收者类型可能为null但null不支持所使用的方法或属性时,访问其属性或调用其方法。不能对具有可空类型的表达式访问属性或调用方法。不过,当该属性或方法为
null所支持(如hashCode或toString())时,则为例外。
健全的空安全将潜在的运行时错误转变为编辑时分析错误。当出现以下情况时,空安全会标记非空变量:
- 未使用非空值进行初始化。
- 被赋值为
null。
这种检查让你能够在部署应用之前修复这些错误。
默认值
具有可空类型且未初始化的变量,其初始值为 null。即使是数值类型的变量,初始值也是 null,因为在 Dart 中,数字与其他所有事物一样,都是对象。
int? lineCount;
assert(lineCount == null);注意
生产环境代码会忽略 assert() 调用。而在开发期间,若 assert(condition) 中的条件为 false,则会抛出异常。详情请查阅 Assert。
启用空安全后,必须在使用非空变量之前对其值进行初始化:
int lineCount = 0;你不必在声明局部变量时就对其进行初始化,但必须在使用它之前为其赋值。例如,以下代码是有效的,因为 Dart 能够检测到在将 lineCount 传递给 print() 时,它是非空的:
int lineCount;
if (weLikeToCount) {
lineCount = countLines();
} else {
lineCount = 0;
}
print(lineCount);顶级变量和类变量是延迟初始化的,初始化代码会在变量首次被使用时运行。
延迟变量
late 修饰符有两种使用场景:
- 声明一个在声明后才被初始化的非空变量。
- 延迟初始化变量。
通常,Dart 的控制流分析能够检测到非空变量在使用前是否已被赋值为非空值,但有时分析会失败。两种常见的情况是顶级变量和实例变量:Dart 通常无法确定它们是否已被设置,因此不会尝试进行检测。
如果你确定某个变量在使用前已被设置,但 Dart 不认同,你可以通过将该变量标记为 late 来修复此错误:
late String description;
void main() {
description = 'Feijoada!';
print(description);
}注意
如果你未能初始化一个 late 变量,那么在使用该变量时会发生运行时错误。
当你将一个变量标记为 late 但在声明时就对其进行初始化时,初始化器会在变量首次被使用时运行。这种延迟初始化在以下几种情况下很有用:
- 变量可能不会被使用,并且初始化它的成本很高。
- 你正在初始化一个实例变量,并且其初始化器需要访问
this。
在以下示例中,如果从未使用 temperature 变量,那么昂贵的 readThermometer() 函数将永远不会被调用:
// 这是程序对 readThermometer() 的唯一调用。
late String temperature = readThermometer(); // 延迟初始化。Final 和 Const
如果你不打算改变一个变量,可使用 final 或 const,既可以替代 var,也可以与类型一起使用。final 变量只能设置一次;const 变量是编译时常量(const 变量隐式为 final)。
注意
实例变量可以是 final,但不能是 const。
以下是创建和设置 final 变量的示例:
final name = 'Bob'; // 没有类型注解
final String nickname = 'Bobby';你不能更改 final 变量的值:
✗ 静态分析:失败
name = 'Alice'; // 错误:final 变量只能设置一次。对需要是编译时常量的变量使用 const。如果 const 变量在类级别,需将其标记为 static const。在声明变量时,将其值设置为编译时常量,如数字或字符串字面量、const 变量,或常量数字的算术运算结果:
const bar = 1000000; // 压力单位(达因/平方厘米)
const double atm = 1.01325 * bar; // 标准大气压const 关键字不仅用于声明常量变量。你还可以用它来创建常量值,以及声明创建常量值的构造函数。任何变量都可以有常量值。
var foo = const [];
final bar = const [];
const baz = []; // 相当于 `const []`你可以在 const 声明的初始化表达式中省略 const,如上面对 baz 的处理。详情见 不要冗余使用 const。
你可以更改非 final、非 const 变量的引用,即使它曾经有一个 const 值:
foo = [1, 2, 3]; // 原来是 const []你不能更改 const 变量的值:
✗ 静态分析:失败
baz = [42]; // 错误:常量变量不能被赋值。你可以定义使用类型检查和类型转换(is 和 as)、集合 if 和展开运算符(... 和 ...?)的常量:
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 的语言版本。
名为 _ 的通配符变量声明一个非绑定的局部变量或参数;本质上是一个占位符。如果有初始化器,它仍然会被执行,但该值无法访问。在同一命名空间中可以存在多个名为 _ 的声明而不会发生冲突错误。
可能影响库隐私的顶级声明或成员不是通配符变量的有效用法。块作用域内的局部声明(如下例)可以声明通配符:
局部变量声明:
dartmain() { var _ = 1; int _ = 2; }For 循环变量声明:
dartfor (var _ in list) {}Catch 子句参数:
darttry { throw '!'; } catch (_) { print('oops'); }泛型类型和函数类型参数:
dartclass T<_> {} void genericFunction<_>() {} takeGenericCallback(<_>() => true);函数参数:
dartFoo(_, this._, super._, void _()) {} list.where((_) => true); void f(void g(int _, bool _)) {} typedef T = void Function(String _, String _);
提示
启用 unnecessary_underscores lint 规则,以识别单个非绑定通配符变量 _ 何时可以替换以前使用多个绑定下划线(__、___ 等)以避免名称冲突的约定。