常量与非常量是针对编译期而言。区别是:
- 常量:用
const
指定,声明变量时就必须指定值,无法再被重新赋值,编译器会展开它的值 - 非常量:
var
:编译器无法在编译期知道它的值final
:跟var
相同,除了它只能被赋值一次,无法再被重新赋值
对于简单类型的字面值
对于简单的字面值,代码写起来是很类似的:
const i1 = 3;
const int i2 = 4;
final i3;
i3 = 5;
i3 = 6; // 错误:i3 只能被设置一次
对于类实例
对于类实例,还是有一些区别。const
要求类实现 const 构造函数(因为它在编译期要保证值无法改变)。final
没有这种要求:
class Vector {
int x, y;
Vector(this.x, this.y);
}
const v1 = Vector(1, 2); // 错误:Vector 类的构造函数不是 const 构造函数
final v2 = Vector(1, 2); // 正确:final 不要求 const 构造函数
v2 = Vector(2, 3); // 错误:final 限制了变量无法被重新赋值
如果你要使用 const 构造函数,那么类中的所有属性必须是 final
的(不能再被赋值):
class Vector {
final int x, y;
const Vector(this.x, this.y);
}
const v1 = Vector(1, 2); // 正确:Vector 类有了 const 构造函数及 final 属性
但 Dart 在设计 const
时有点奇怪,const
不仅能用来修饰一个变量(如上面的 v1
),它还可以修饰对象的创建过程。事实上:
const v1 = Vector(1, 2);
// 等价于
const v1 = const Vector(1, 2);
// 只是这种 const 编译器可以推断出来,所以可以省略掉
// 你也可以
var v2 = const Vector(1, 3);
// v2 非 const 仅表示它可以被重新赋值
v2 = const Vector(2, 3);
// 但它的值仍然是一个 const 对象,仍然无法修改它的属性
v2.x = 3; // 错误:Vector.x 是 final 的
但是对象的创建过程加不加 const
都是没有意义的,因为 const 构建函数决定了创建出来的对象必然是 const 的。
如上所示,一旦构建函数是 const 的,你就只能初始化出常量的 Vector
实例。但是实际上经常需要用到非常量的实例。因此实践上最好上写两个 Vector
类:
class Vector {
int x, y;
Vector(this.x, this.y);
}
class ImmutableVector {
final int x, y;
const Vector(this.x, this.y);
}
const
从机制上保证了对象各属性值是编译期确定好的,所以你无法再修改 v1.x
的值;但是 final
没有这种保证:
class Vector {
int x, y;
Vector(this.x, this.y);
}
final v2 = Vector(1, 2);
v2.x = 3; // 正确:Vector.x 不是 final 的,可以被修改
无法被修改的集合
Dart 为了实现不可变的集合,复用了 const
关键字,产生了一些不太直观的代码。比如一个无法被修改的 List,可以这样创建:
var foo = const [];
final bar = const [];
const baz = []; // 等同于 `const []`
// 下面几种操作都会在运行时报错:
// Unsupported operation: Cannot add to an unmodifiable list
bar.add(1);
foo.add(1);
baz.add(1);
同样的,Set 和 Map 也有类似的方法:
var m = const {};
// 运行时报错:
// Unsupported operation: Cannot set value in unmodifiable Map
m['a'] = 1;
var s = const <dynamic>{};
// 运行时报错:
// Unsupported operation: Cannot change unmodifiable set
s.add('a');
Dart 在这块的设计不够优雅。const
关键字被滥用了,它可以:
- 修饰一个变量,表示它是编译期确定的常量
- 修饰一个 const object 的创建过程,而且并没有什么用
- 作为不可修改集合(unmodifiable collection)的字面值
这很容易引起混淆。很多人看到 const [1, 2]
这种语法应该会一头雾水。对比之下 Python 的做法则没有引入新的语法机制:
- 可变列表用
[1, 2]
,不可变列表(tuple,元组)用(1, 2)
- 可变集合用
set({1, 2})
或者字面值{1, 2}
,不可变集合用frozenset({1, 2})