dart

Dart 基础 – final 和 const

内容纲要

版权归作者 ©刘龙宾 所有,本文章未经作者允许,禁止私自转载!

1. final 和 const 的共同点

无论是 final 还是 const,都可以用来定义那些不允许被修改的变量值。这一特点和其它语言中的“常量”类似。举个例子:

void main(List<String> args) {
  // 1. 使用 final 定义常量 name 并赋值
  final String name = 'zs';
  print(name);

  // 2. 使用 const 定义常量 nickname 并赋值
  const String nickname = 'ls';
  print(nickname);

  // 3. 无论 name 还是 nickname 都无法使用 = 重新赋值,
  //    所以下面的两行代码会编译失败
  name = 'liulongbin';
  nickname = 'escook';
}

因此,我们今后如果想定义常量并立即赋初值(注意:这里的初值特指明确的字面量值),无论使用 final 还是 const 都可以。

2. final 和 const 的区别

正所谓存在即合理,dart 既然提供了 final 和 const 这两个关键字,那么它们在某些特定场景下肯定存在差别,我们需要了解并掌握以下 3 点区别。

2.1 区别 1

const 表示编译时必须给出确切的最终值,final 表示运行时可以动态生成一个最终的值。

第一个区别是最重要的。掌握第一个区别的关键能否正确区分什么叫“确切的最终值”,什么叫“动态生成最终值”。

  1. 确切的最终值:即字面量值,例如数字 1,字符串 'abc',布尔值 true,对象、数组等都是明确的值。
  2. 动态生成最终值:即值是动态生成的,例如随机数动态获取系统的时间等。

例如,下面的代码动态生成了 10 以内的随机整数,并把随机生成的整数赋值给常量 age,此时定义常量 age 时不能使用 const,只能使用 final:

// 导入 核心类库 math
import 'dart:math';

void main(List<String> args) {
  // 调用 Random() 得到一个随机数生成器
  // 再调用随机数生成器的 nextInt(n) 函数,生成 0-n 之间的随机整数(包括0,但不包括n)
  // 这里不能用 const,只能用 final。因为 Random().nextInt(10) 的值“是在程序运行时动态生成的”!
  final int age = Random().nextInt(10);
  print(age);
}

再例如,把当前时间赋值给常量 dt,在定义常量 dt 时不能使用 const,只能用 final。因为 dt 的值不是写死的字面量值,而是程序运行时动态生成的:

void main(List<String> args) {
  // 这里只能用 final
  final DateTime dt = DateTime.now();
  print(dt);
}

最后,还有一个注意点需要特别提醒下:如果我们想定义一个空的常量,随后再给这个常量赋值,此时只能用 final。示例代码如下:

void main(List<String> args) {
  // 1. 使用 final 先定义名为 name 的空白常量
  final String name;
  // 2. 在程序运行过程中,动态把常量 name 的值赋值为字符串 'liulongbin'
  name = 'liulongbin';
  print(name);
}

2.2 区别 2

const 定义的常量值在内存中会被复用;final 定义的常量哪怕值相同,在内存中也会被重复定义。

使用 const 定义两个字符串的数组,分别是 s1 和 s2。调用 identical(对象1, 对象2) 函数可以判断两个对象的内存地址是否相同。示例代码如下:

void main(List<String> args) {
  // 使用 const 定义两个常量 s1 和 s2,分别指向“自己的”字面量数组
  const List<String> s1 = ['a', 'b', 'c'];
  const List<String> s2 = ['a', 'b', 'c'];

  // identical() 函数用来检查两个对象是否指向同一个内存地址。
  // 打印的结果是 true,
  // 证明 s1 和 s2 指向同一个内存地址,s1 和 s2 共用1个 ['a', 'b', 'c'] 存储空间,
  // 即 “const 定义的常量值在内存中会被复用”
  print(identical(s1, s2));
}

如果把上述代码中的 const 替换成 final,再次调用 identical(对象1, 对象2) 函数,发现打印的值变成了 false。示例代码如下:

void main(List<String> args) {
  // 使用 final 定义两个常量 s1 和 s2,分别指向“自己的”字面量数组
  final List<String> s1 = ['a', 'b', 'c'];
  final List<String> s2 = ['a', 'b', 'c'];

  // identical() 函数用来检查两个对象是否指向同一个内存地址。
  // 打印的结果是 false,
  // 证明 s1 和 s2 指向不同的内存地址,在内存中分别为 s1 和 s2 开辟了自己的 ['a', 'b', 'c'] 存储空间,
  // 即 “final 定义的常量哪怕值相同,在内存中也会被重复定义”
  print(identical(s1, s2));
}

2.3 区别 3

非 final,非 const 的变量是可以被修改的,即使这些变量曾经引用过 const 值。

变量的本质是“数据可变性”。如果变量曾经引用过其它常量的值,也不会影响我们后续修改这个变量的值。示例代码如下:

void main(List<String> args) {
  // 1. 定义 const 常量 name 并赋初值
  const name = 'liulongbin';
  // 2. 把常量 name 的值赋值给变量 username
  //  ★变量 username 引用了常量 name 的值
  var username = name;
  // 3. 输出 username 的值是字符串 liulongbin
  print(username);

  // 4. 把变量 username 的值变更为字符串 escook
  //  ★虽然变量 username 曾经引用过常量 name 的值,
  //  ★但是,这不会影响我们为 username 重新赋值,因为 username 本质上是“变量”
  username = 'escook';
  // 5. 再次输出 username 的值,是字符串 escook
  print(username);
}

3. 总结

  1. const 和 final 都可以用来定义那些不允许被修改的变量值。如果初始值是字面量,那么后续不能修改它们的值。
  2. 如果常量的值是运行时动态生成的,则只能使用 final 关键字来定义常量。
  3. const 定义的常量值在内存中会被复用,而 final 定义的常量值哪怕看上去一样,在内存中也会开辟不同的空间进行存储。
  4. 变量哪怕曾经引用过常量的值,也不影响我们后续给这个变量赋新值。

版权归作者 ©刘龙宾 所有,本文章未经作者允许,禁止私自转载!

一个自由の前端程序员

4条评论

留言

您的电子邮箱地址不会被公开。 必填项已用*标注