【Dart 入门日记】第2篇:常量声明详解——constfinal 到底有什么区别?

作者:灰灰勇闯IT
时间:2026年1月
系列定位:零基础记录 Dart 学习过程,适合编程新手、Flutter 初学者
本文重点:深入理解 const(编译时常量)与 final(运行时常量)的使用场景与本质区别

目录


1. 为什么需要常量?

在编程中,有些值从诞生起就不该被修改。比如:

  • 数学常数:π(3.14159…)、e(2.718…)
  • 配置项:API 地址、版本号
  • 时间戳:用户点击按钮的精确时刻

如果这些值被意外修改,程序逻辑就会出错。因此,Dart 提供了两种“不可变”机制:constfinal

🌟 我的思考
昨天学了 var,今天学“不能变的变量”,感觉像是在给数据上锁——有的锁在写代码时就焊死了(const),有的锁在程序跑起来那一刻才扣上(final)。


2. const:编译期就确定的“绝对不变”值

2.1 场景引入:圆周率 π 不可更改

计算圆的周长公式是:C = 2 × π × r
其中,π 是一个数学常数,永远不变。这种值就应该用 const 声明。

2.2 const 基本语法与代码示例

语法:

const 常量名 =;

示例:计算半径为 5 的圆的周长

void main() {
  const pi = 3.1415926; // 编译时常量
  var radius = 5.0;
  var circumference = 2 * pi * radius;
  print('半径为 $radius 的圆,周长是 $circumference');
}

输出:

半径为 5.0 的圆,周长是 31.415926

!![[图片:932ad3c1d3ac4d22b724ccacb0101011.png](图:使用  声明 π 并成功计算周长)](https://i-blog.csdnimg.cn/direct/932ad3c1d3ac4d22b724ccacb0101011.png#pic_center)

特点

  • 值在编译时就必须确定
  • 所有 const 对象在内存中是单例(相同值只存一份)
  • 可用于注解、元数据等需要编译期常量的场景

2.3 ❌ 常见错误:const 表达式中不能包含变量

const 的值必须是“字面量”或由其他 const 组成的表达式,不能依赖运行时才能知道的变量

错误示例:

void main() {
  var radius = 5.0;
  // ❌ 错误!radius 是变量,无法在编译时确定
  const area = 3.1415926 * radius * radius;
}

报错信息类似:

Error: Constant expressions can't refer to non-constant variables.

[图片:2344a96146ab4fdbb514d84dd8824ab8.png](图:尝试在  中使用变量导致编译错误)

💡 正确做法:改用 final,或者把整个表达式移到运行时计算。


3. final:运行时才确定的“一次赋值”变量

3.1 场景引入:获取当前时间作为唯一操作时间戳

假设我们要记录用户点击按钮的精确时间。这个时间只有在程序运行到那一行代码时才能知道,但它一旦获取,就不应再变。

这就是 final 的典型用武之地。

3.2 final 基本语法与代码示例

语法:

final 变量名 =;

示例:获取当前年份

import 'dart:core';

void main() {
  final now = DateTime.now(); // 运行时获取当前时间
  final currentYear = now.year;
  print('当前年份是:$currentYear');
}

输出(假设今天是 2026 年):

当前年份是:2026

[图片:623f25a2821742c1a5ab2c6639278536.png](图:使用  获取当前时间并输出年份)

特点

  • 值在运行时第一次赋值后锁定
  • 可以使用变量、函数返回值等动态内容初始化
  • 每次运行程序,final 的值可能不同(如时间、随机数)

4. 核心对比:const vs final —— 编译时 vs 运行时

特性 const final
初始化时机 编译时 运行时
值是否可变 绝对不可变 初始化后不可变
能否使用变量初始化 ❌ 不行 ✅ 可以
内存优化 相同值共享同一对象(单例) 每次创建新对象
适用场景 数学常量、配置字面量 时间、用户输入、API 返回值

代码对比示例:

void main() {
  // ✅ const:编译期确定
  const pi = 3.1415926;
  const appName = "My Flutter App";

  // ✅ final:运行期确定
  final timestamp = DateTime.now().millisecondsSinceEpoch;
  final randomId = DateTime.now().microsecondsSinceEpoch % 1000;

  print('App: $appName, π ≈ $pi');
  print('时间戳: $timestamp, 随机ID: $randomId');
}

5. 深入理解:为什么 Dart 要区分这两种常量?

这背后是 性能灵活性 的权衡:

  • const 用于极致优化
    因为值在编译时已知,Dart 可以:

    • 将相同 const 对象合并(节省内存)
    • 在 AOT 编译(如 Flutter Release 模式)中直接内联值
    • 支持用作 Widget 构造函数的 const 参数(提升 UI 渲染性能)
  • final 保证运行时安全
    当值依赖外部输入(如网络、传感器、时间),final 确保“只读一次”,防止后续逻辑意外修改。

📌 Flutter 开发提示
在构建 Widget 时,尽量用 const 修饰那些不会变的子组件,例如:

const Text('欢迎来到我的 App', style: TextStyle(fontSize: 18));

这能显著减少重建开销!


6. 实战建议:什么时候用 const,什么时候用 final

✅ 用 const 当:

  • 值是字面量(数字、字符串、布尔值)
  • 值由其他 const 组合而成
  • 用于配置、枚举、UI 静态文本
const double gravity = 9.8;
const List<String> weekdays = ['Mon', 'Tue', 'Wed'];

✅ 用 final 当:

  • 值来自函数调用(如 DateTime.now()
  • 值来自参数或用户输入
  • 值在构造函数中初始化(类成员)
class User {
  final String name;
  final int loginTime;

  User(this.name) : loginTime = DateTime.now().millisecondsSinceEpoch;
}

7. 常见误区与调试技巧

❌ 误区1:“finalconst 一样,都是常量”

→ 不完全对!final 是运行时常量,const 是编译时常量,性能和使用限制完全不同

❌ 误区2:“const 可以放在函数里动态计算”

→ 不行!const 必须在编译时就能算出结果。

✅ 调试技巧:

  • 在 VS Code 中,悬停 const 变量会显示 “compile-time constant”
  • 尝试给 const 赋变量值,编译器会立即报错,这是好事!
  • 在 Flutter 中,开启 “Performance Overlay” 可观察 const Widget 是否被跳过重建

8. 小结 & 下期预告

本篇收获

  • 掌握了 const 的使用:适用于编译期已知的绝对常量(如 π)
  • 理解了 final 的价值:适用于运行时确定但后续不变的数据(如时间戳)
  • 明确了二者的核心区别:编译时 vs 运行时
  • 避开了“在 const 中使用变量”的经典错误

🎯 动手练习
尝试写一个 Circle 类,用 const 存储 π,用 final 存储半径,并提供计算面积和周长的方法。


➡️ 下期预告
《【Dart 入门日记】第3篇:Dart 的基础数据类型全解析——int、double、String、bool 与 null 安全》
我们将系统学习每一种内置类型,并揭开 Dart 空安全(Null Safety)的神秘面纱!


💬 互动时间
你在项目中更常用 const 还是 final?有没有因为混淆两者而踩过坑?欢迎在评论区分享你的故事!如果你觉得这篇文章帮你理清了概念,别忘了 点赞 + 收藏 + 关注,你的支持是我持续更新的最大动力!


📎 附:所有代码均可直接运行
环境要求:Dart SDK 3.0+,推荐使用 VS Code + Dart/Flutter 插件


Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐