文章

运行时常量和编译时常量区别及什么时候使用

运行时常量 和 编译时常量区别及什么时候使用

运行时常量和编译时常量区别及什么时候使用

运行时常量 和 编译时常量区别及什么时候使用

核心区别

特性编译时常量 (const)运行时常量 (final)
确定时间编译时(代码转换成机器指令之前)运行时(程序运行过程中)
值来源必须在代码中直接明确指定可以在运行时计算或赋值
内存分配在内存中只有一份实例( canonicalized)每次初始化都会创建实例
性能更高(编译时即确定,无运行时开销)一般(有运行时赋值开销)
使用场景值在编译时就能确定且不变值在运行时才能确定,但确定后不变

详细解释与示例

1. 编译时常量 (const)

定义:在代码编译阶段(即程序运行前)值就必须完全确定且不可变的常量。

特点

  • 值必须是字面量或由其他编译时常量构成的计算表达式
  • 可以使用 const 构造函数创建编译时常量对象
  • 在内存中是单例的(相同的 const 值只会存在一份)

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 正确:字面量常量
const int maxCount = 100;
const String appName = 'MyApp';
const List<int> fixedNumbers = [1, 2, 3]; // const 列表
const Map<String, int> config = {'timeout': 30, 'retries': 3};

// 正确:由其他常量组成的表达式
const double total = maxCount * 1.5;
const Duration timeout = Duration(seconds: 30); // 使用 const 构造函数

// 错误:值在运行时才能确定
const currentTime = DateTime.now(); // ❌ 编译错误
const randomValue = Random().nextInt(10); // ❌ 编译错误

// const 构造函数的使用
class MyPoint {
  final double x;
  final double y;

  const MyPoint(this.x, this.y); // const 构造函数
}

const origin = MyPoint(0, 0); // 编译时常量对象

2. 运行时常量 (final)

定义:在程序运行时赋值,但一旦赋值后就不可改变的变量。

特点

  • 值可以在运行时通过计算、函数调用、构造函数等方式确定
  • 每次初始化都会创建新的实例
  • 更加灵活,适用于大多数”赋值后不变”的场景

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 基本用法
final int userId = 123;
final String userName = 'John Doe';

// 运行时计算的值
final double calculatedValue = calculateSomeValue(); // 调用函数
final DateTime currentTime = DateTime.now(); // ✅ 运行时确定
final int randomNumber = Random().nextInt(100); // ✅ 运行时确定

// 在构造函数中初始化
class UserProfile {
  final String name;
  final int age;

  UserProfile(this.name, this.age); // 普通构造函数
}

final user = UserProfile('Alice', 25); // 运行时创建的对象

3. 结合使用 (static const)

在类中定义共享的编译时常量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class AppConstants {
  // 类级别的编译时常量
  static const String appName = 'MyFlutterApp';
  static const double defaultPadding = 16.0;
  static const Duration animationDuration = Duration(milliseconds: 300);

  // 错误:不能在类中这样声明
  // final String version = '1.0.0'; // ❌ 需要 static final
  static final String buildTime = DateTime.now().toString(); // ✅
}

// 使用
Text(
  AppConstants.appName,
  style: TextStyle(fontSize: AppConstants.defaultPadding),
);

什么时候使用哪种?

使用 const 当:

值是字面量且在编译时已知

  1. dart
1
2
const apiKey = 'abc123'; // 如果确实是固定值
const maxRetries = 3;

创建不可变的集合

  1. dart
1
2
const supportedLanguages = ['en', 'es', 'fr']; // 不可变列表
const defaultSettings = {'theme': 'light', 'notifications': true}; // 不可变映射

使用 const 构造函数提高性能

  1. dart
1
2
3
const EdgeInsets.all(16.0); // 而不是 EdgeInsets.all(16.0)
const BorderRadius.circular(8.0);
const Color(0xFF4285F4);

定义全局或类的常量值

  1. dart
1
2
3
4
class AppColors {
  static const primary = Color(0xFF6200EE);
  static const accent = Color(0xFF03DAC5);
}

使用 final 当:

值在运行时才能确定

  1. dart
1
2
final currentUser = fetchUserFromAPI(); // API调用
final deviceSize = MediaQuery.of(context).size; // 依赖上下文

类的实例变量(大多数情况下)

  1. dart
1
2
3
4
5
6
class User {
  final String id;
  final String email;

  User(this.id, this.email); // 这些值在运行时通过构造函数传入
}

需要计算的值

  1. dart
1
2
final totalPrice = quantity * unitPrice; // 运行时计算
final fullName = '$firstName $lastName'; // 运行时拼接

Flutter 中的实践建议

在 Flutter 开发中,正确使用 const 可以显著提高性能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Good: 使用 const 构造函数
Widget build(BuildContext context) {
  return const Text( // const Widget
    'Hello World',
    style: TextStyle(fontSize: 16), // 但 TextStyle 不是 const
  );
}

// Better: 尽可能多的使用 const
Widget build(BuildContext context) {
  return const Text(
    'Hello World',
    style: TextStyle(fontSize: 16), // 这个还不是 const
  );
}

// 将样式也提取为 const
static const _textStyle = TextStyle(fontSize: 16);

Widget build(BuildContext context) {
  return const Text(
    'Hello World',
    style: _textStyle, // 现在整个 Widget 树都是 const 了
  );
}

总结

  • const 优化性能:用于编译时已知的常量值、集合和 Widget
  • final 保证安全性:用于运行时确定的、赋值后不变的值
  • 记住:所有**** ****const** **变量隐含也是** **final** ****的,但反过来不成立**
本文由作者按照 CC BY 4.0 进行授权