变量
学习 Dart 中的变量。下面是创建并初始化变量的示例:
var name = 'Jack';Code language: JavaScript (javascript)
变量存储的是引用,名为 name 的变量中存放着一个引用,该引用指向值为 “Bob” 的字符串对象。
引用就是地址,内存中的地址,为了方便内存存储数据,给每个内存区域标上一个地址。相当于家里的门牌一样。

编译器会自动推导 name 的类型为 String,但你也可以手动指定类型来改变推导结果。如果一个变量需要存放多种类型的对象,可以将其类型声明为 Object(必要时也可以使用 dynamic)。
Object name = 'Jack';Code language: JavaScript (javascript)
另一种写法是显式指定类型(例如我们知道名字是文本,可以用字符串String存储,那么我们直接指定字符串就可以):
String name = 'Jack';Code language: JavaScript (javascript)
Null safety 空安全
空安全可以避免因误访问值为 null 的变量而引发的错误,这类错误被称为空值引用错误( null dereference error)。当你对一个运算结果为 null 的表达式访问属性或调用方法时,就会触发空值引用错误。
有一个例外:如果 null 本身支持该属性或方法(例如 toString()、hashCode),则不会报错。借助空安全,Dart 编译器会在编译阶段就识别出这类潜在错误。
有编程经验的读者应该知道,空引用的错误,是很常见的。如果可以在编译阶段就避免这个错误,那么程序的稳定性健壮性会大大提高。
举个例子:假设你想获取 int 类型变量 i 的绝对值。如果 i 是 null,执行 i.abs() 就会触发空解引用错误。在其他编程语言中,这段代码只会在程序运行时报错;而 Dart 编译器会直接禁止这类非法调用,提前拦截问题。
空安全带来三项核心改动:
1 可空类型,指定类型时,你可以控制该类型是否允许为空。只需要在类型末尾加一个问号就可以。
String? name // 可空类型:值可以是 null 或字符串
String name // 非可空类型:不能为 null,只能存放字符串Code language: JavaScript (javascript)
在有些编程语言中,string name 都可以存放null的,但是在dart中,是不可以的。dart如果要存放null,就需要string? 这个类型。
2 变量在使用前必须完成初始化。可空变量默认值就是 null,相当于自带初始化;而非可空类型没有默认值,强制你手动赋予初始值。Dart 不允许访问未初始化的变量,以此杜绝一种情况
void main() {
int year; // 错误:未初始化的非可空变量
print(year); // 编译错误:year 未初始化
}
Code language: Dart (dart)
以上编译会报错
C:\dartdemo\firstdart>dart run
Building package executable...
Failed to build firstdart:firstdart:
bin/firstdart.dart:7:9: Error: Non-nullable variable 'year' must be assigned before it can be used.
print(year); // 编译错误:year 未初始化
^^^^Code language: JavaScript (javascript)
如果是可空变量 是可以这样的
void main() {
int? age; // 可空变量,默认值为 null
print(age); // 输出null 但不报错
//age = 18; // 手动赋值
//print(age); // 输出 18
}Code language: JavaScript (javascript)
3 不能直接对可空类型的表达式访问属性、调用方法。同样存在例外:如果是 null 原生支持的属性或方法(hashCode、toString()),则允许调用。
void main() {
String? name; // 可空类型,默认值为 null
// 错误:直接访问 length 会报错
// print(name.length);
// 正确:允许调用 null 原生支持的方法
print(name.toString()); // 输出 "null"
print(name.hashCode); // 输出一个整数
// 正确:使用空安全操作符
print(name?.length); // 输出 null,不报错
}Code language: PHP (php)
默认值
未手动赋值的可空变量,初始值默认为 null。即便是数字类型变量,默认值也是 null,
因为在 Dart 中,数字和其他所有数据一样,本质都是对象。
void main() {
int? lineCount;
assert(lineCount == null);
}Code language: JavaScript (javascript)
注意,生产环境代码会忽略
assert()校验;但在开发阶段,若assert(条件)内的条件不成立,程序会抛出异常
开启空安全后,非可空变量使用前必须完成初始化:
int lineCount = 0;
局部变量不必在声明的同一行赋值,但必须在使用前完成赋值。下面这段代码是合法的,因为 Dart 能分析出执行 print() 时 lineCount 一定是非空值:
int lineCount;
if (weLikeToCount) {
lineCount = countLines();
} else {
lineCount = 0;
}
print(lineCount);
Code language: Dart (dart)
注意上面代码lineCount = countLines();和lineCount = 0;是给lineCount赋值,并不算是使用,使用意思是用到里面的值,是读取的意思。例如下面代码就会报错
void main() {
int lineCount;
print(lineCount);
}
Code language: Dart (dart)
编译报错

顶层变量与类成员变量属于延迟初始化:只有第一次被使用时,才会执行赋值逻辑。
late 修饰变量
late 修饰符有两种使用场景:
- 声明非可空变量,后续再为其赋值;
- 实现变量延迟初始化。
大多数情况下,Dart 的流程分析可以识别出:某个非可空变量在使用前已经被赋予非空值。但部分场景分析会失效,最常见的两种就是顶层变量和实例变量:编译器通常无法判断它们是否完成赋值,因此不会自动放行。
如果你能确定变量在使用前一定会被赋值,但编译器识别不到,就可以给变量添加 late 修饰来消除报错:
如下代码 description是全局变量,可能无法识别出是否赋值,但是你确定它绝对会赋值的,可以使用late 修饰
late String description;
void main() {
description = 'Feijoada!';
print(description);
}
Code language: Dart (dart)
提醒
若声明了
late变量却始终没有赋值,当代码访问该变量时会抛出运行时异常。
如果你在声明 late 变量的同时直接给出初始值,那么这段初始化代码只会在变量第一次被访问时执行。这种延迟初始化在两种场景下十分实用:
- 该变量有可能全程不会被使用,且初始化操作开销很大;
- 初始化实例变量时,初始化逻辑需要访问
this。
下面示例中,如果全程没有使用 temperature,开销较大的 readThermometer() 函数永远不会执行:
// 本程序仅在访问 temperature 时才会调用 readThermometer()
late String temperature = readThermometer(); // 延迟初始化
Code language: JavaScript (javascript)
final 与 const
如果你不希望变量的值被修改,可以使用 final 或 const;两者可以单独使用,也可以搭配类型标注,替代 var。
final变量只能赋值一次;const变量是编译期常量(const变量隐式带有final特性)。
注意
实例成员变量可以用
final修饰,但不能使用const;类级别的常量请使用static const。
下面是定义 final 变量的示例:
final name = 'Jack'; // 不标注类型
final String nickname = 'Jason';
Code language: PHP (php)
你无法修改 final 变量的值,编译器会直接报错:
void main() {
final name = 'Jack'; // 不标注类型
final String nickname = 'Jason';
name = 'Alice'; // 报错:final 变量仅允许赋值一次
}Code language: JavaScript (javascript)
C:\dartdemo\firstdart>dart run
Building package executable...
Failed to build firstdart:firstdart:
bin/firstdart.dart:6:2: Error: Can't assign to the final variable 'name'.
name = 'Alice'; // 报错:final 变量仅允许赋值一次Code language: PHP (php)
const 用于定义编译期常量。如果常量定义在类内部,需要加上 static 修饰为 static const。声明 const 变量时,赋值内容必须是编译期可确定的值,例如数字、字符串字面量、其他 const 变量,或是常量数字参与算术运算的结果:
const bar = 1000000; // 压强单位(达因/平方厘米)
const double atm = 1.01325 * bar; // 标准大气压
Code language: JavaScript (javascript)
const 关键字不只是用来声明常量变量,你还可以用它创建常量对象,或是定义能生成常量实例的构造函数。任意变量都可以接收一个常量对象作为值。
var foo = const [];
final bar = const [];
const baz = []; // 等价于 const []
Code language: PHP (php)
在 const 变量的赋值表达式中可以省略 const 关键字,就像上面 baz 的写法。
即便变量曾经接收常量对象,只要它不是 final、也不是 const,就可以修改它的引用指向:
foo = [1, 2, 3]; // foo 原本指向常量空数组,现在可以更换引用
Code language: JavaScript (javascript)
但 const 变量不允许重新赋值,编译器会报错:
// 静态分析:代码错误
baz = [42]; // 报错:常量变量不允许重新赋值
Code language: JavaScript (javascript)
定义常量时,可以搭配类型判断、类型强转(is、as)、集合 if、展开运算符(...、...?):
void main() {
const Object i = 3; // i 是 Object 类型常量,内部存放 int 值
const list = [i as int]; // 使用类型强转
const map = {if (i is int) i: 'int'}; // 使用 is 判断与集合if
const set = {if (list is List<int>) ...list}; // 搭配展开运算符
}Code language: JavaScript (javascript)
注意
final修饰的对象本身引用不可修改,但对象内部的字段可以更改;与之对比,const修饰的对象及其内部所有数据都不可变更,属于完全不可变。
想要了解更多使用 const 创建常量集合的内容,请查看 列表、映射、类 相关文档。
* 通配符变量
版本说明,通配符变量要求 Dart 语言版本至少为 3.7。
以 _ 命名的通配符变量是无绑定局部变量 / 参数,本质只是占位符。如果带有初始化表达式,表达式依然会执行,但你无法读取该变量存储的值。同一作用域内可以定义多个名为 _ 的变量,不会出现命名冲突。
什么是通配符变量
Dart 中单个下划线 _ 就是通配符变量:
- 用来接收不需要使用的值,告诉编译器 “这个变量我不会读取、不会用到”;
- 编译器不会提示「变量定义未使用」警告;
- 不能读取
_(读取会报错),只能用来占位丢弃数据。
// 只取第1、3个值,第二个用 _ 丢弃
final [a, _, c] = [10, 20, 30];
print(a); // 10
print(c); // 30
// print(_); // 报错,不能读取通配符Code language: PHP (php)
顶层声明、会影响库私有访问权限的类成员,不允许使用通配符变量。仅块级作用域内的声明可以使用,示例如下:
- 局部变量声明
void main() {
var _ = 1;
int _ = 2;
}
Code language: JavaScript (javascript)
- for 循环变量
for (var _ in list) {}
Code language: PHP (php)
- catch 捕获参数
try {
throw '!';
} catch (_) {
print('oops');
}
Code language: PHP (php)
- 泛型类型、函数泛型参数
class T<_> {}
void genericFunction<_>() {}
takeGenericCallback(<_>() => true);
Code language: JavaScript (javascript)
- 函数参数
Foo(_, this._, super._, void _()) {}
list.where((_) => true);
void f(void g(int _, bool _)) {}
typedef T = void Function(String _, String _);Code language: JavaScript (javascript)