Dart 中的 final 关键字
最后修改于 2025 年 5 月 25 日
本教程将探讨 Dart 中的 final 关键字,它是一种用于声明只能设置一次的变量的修饰符。Final 变量在运行时进行初始化,并为不应在赋值后更改的值提供不变性。
Final 关键字概述
Dart 中的 final 关键字用于声明只能赋值一次的变量。与 const 不同,final 变量在运行时而非编译时进行初始化。这使得它们更加灵活,同时仍然强制执行不变性。
Final 变量必须在使用前进行初始化,可以在声明时初始化,也可以在构造函数中初始化(对于实例变量)。一旦赋值,它们的值就不能更改。Final 通常用于在运行时已知但之后不应更改的值。
| 功能 | Final | Const | Var |
|---|---|---|---|
| 可变性 | 不可变的 | 不可变的 | 可变的 |
| 初始化 | 运行时 | 编译时 | 运行时 |
| 重新赋值 | 否 | 否 | 是 |
| 用例 | 运行时常量 | 编译时常量 | 常规变量 |
当您需要一个值在初始化后保持不变,但该值仅在运行时才知道时,Final 特别有用。它有助于防止意外修改,使代码更可预测和可维护。
基本 Final 变量
此示例演示了 final 的最简单用法,即声明一个只能设置一次的变量。我们将创建一个 final 变量并尝试修改它。
void main() {
// Declaring a final variable
final String greeting = 'Hello, Dart!';
print(greeting);
// This would cause a compile-time error:
// greeting = 'Goodbye'; // Error: Can't assign to the final variable 'greeting'
// Final variables can be initialized only once
final currentTime = DateTime.now();
print('Current time: $currentTime');
// This would also cause an error:
// currentTime = DateTime.now(); // Error: Can't assign to the final variable 'currentTime'
}
该示例显示了一个不能重新赋值的 final 字符串变量。它还演示了 final 变量可以用运行时值(如当前时间)进行初始化,而 const 无法做到。注释掉的行显示了尝试重新赋值 final 变量,这会导致错误。
Final 变量在提供类型安全和不变性的同时,允许在运行时确定值。这使得它们比 const 更灵活,同时仍然防止意外修改。
$ dart run basic_final.dart Hello, Dart! Current time: 2025-05-25 14:30:45.123456
Final 与集合
Final 可与集合(List、Map、Set)一起使用,使引用不可变,但允许修改集合的内容。此示例显示了 final 如何影响集合的行为。
void main() {
// Final list - reference cannot change but content can
final List<int> numbers = [1, 2, 3];
print('Original list: $numbers');
// Can modify the list's contents
numbers.add(4);
numbers[0] = 10;
print('Modified list: $numbers');
// Cannot reassign the list
// numbers = [5, 6, 7]; // Error: Can't assign to the final variable 'numbers'
// Final map example
final Map<String, int> ages = {'Alice': 30, 'Bob': 25};
ages['Charlie'] = 28;
print('Ages: $ages');
// Final set example
final Set<String> colors = {'red', 'green'};
colors.add('blue');
print('Colors: $colors');
}
该示例演示了虽然对 final 集合的引用不能更改,但集合的内容仍然可以修改。这与 const 集合不同,后者引用和内容都是不可变的。
当您希望确保变量始终引用同一个集合实例,但仍然需要在程序执行期间修改集合内容时,此行为很有用。
$ dart run final_collections.dart
Original list: [1, 2, 3]
Modified list: [10, 2, 3, 4]
Ages: {Alice: 30, Bob: 25, Charlie: 28}
Colors: {red, green, blue}
Final 实例变量
Final 通常用于类中的实例变量,以创建不可变属性。这些可以在声明时或在构造函数中初始化。
class Person {
// Final instance variable initialized at declaration
final String species = 'Human';
// Final instance variables initialized in constructor
final String name;
final int birthYear;
Person(this.name, this.birthYear);
int get age {
final currentYear = DateTime.now().year;
return currentYear - birthYear;
}
void describe() {
print('$name is a $species born in $birthYear (age $age)');
}
}
void main() {
final person = Person('Alice', 1990);
person.describe();
// These would cause errors:
// person.name = 'Bob'; // Error: Can't assign to the final variable 'name'
// person.species = 'Alien'; // Error: Can't assign to the final variable 'species'
// Final local variable in method
final message = 'Hello, ${person.name}';
print(message);
}
Person 类演示了在声明时以及通过构造函数初始化的 final 实例变量。一旦设置,这些值就不能更改,确保对象的属性在创建后保持不变。
该示例还显示了方法中的 final 局部变量以及使用 final 变量的 getter。Final 变量通常用于类中创建不可变数据模型,其中属性在实例化后不应更改。
$ dart run final_instance.dart Alice is a Human born in 1990 (age 35) Hello, Alice
Final 与 Late 初始化
Dart 的 late 修饰符可以与 final 结合使用,以推迟初始化,同时仍然确保变量只能设置一次。这对于依赖注入或延迟初始化非常有用。
class DatabaseService {
void connect() => print('Database connected');
}
class AppConfig {
// Late final variable - must be initialized before use
late final String apiKey;
void initialize(String key) {
apiKey = key;
print('API key set to: $apiKey');
// This would cause an error:
// Error: Late final variable 'apiKey' is already initialized
// apiKey = 'new-key';
}
}
void main() {
// Late final variable initialized when needed
late final DatabaseService dbService;
print('Initializing application...');
dbService = DatabaseService();
dbService.connect();
final config = AppConfig();
// Error: LateInitializationError if accessed before initialization
print(config.apiKey);
config.initialize('abc123xyz');
}
该示例显示了在声明后但在使用前初始化的 late final 变量。late 修饰符允许推迟初始化,而 final 确保变量只能设置一次。当值不立即可用但必须在使用前设置时,此模式很有用。
尝试在初始化前访问 late final 变量会引发 LateInitializationError。这提供了运行时安全性,以确保变量在使用前已正确初始化。
$ dart run late_final.dart Initializing application... Database connected API key set to: abc123xyz
函数参数中的 Final
Final 可用于函数参数,以防止在函数内修改参数。这有助于记录不应更改参数。
import 'dart:math';
void processUser(final String username, final int userId) {
// These would cause errors:
// username = 'new_name'; // Error: Can't assign to the final variable 'username'
// userId = 456; // Error: Can't assign to the final variable 'userId'
print('Processing user $userId: $username');
final localId = userId; // Can create new final variables from parameters
final message = 'Welcome, $username';
print(message);
}
class Point {
final double x, y;
Point(final this.x, final this.y);
Point.origin()
: x = 0,
y = 0;
double distanceTo(final Point other) {
final dx = x - other.x;
final dy = y - other.y;
return sqrt(dx * dx + dy * dy);
}
}
void main() {
processUser('alice123', 123);
final p1 = Point(3, 4);
final p2 = Point.origin();
print('Distance: ${p1.distanceTo(p2)}');
}
该示例演示了函数和构造函数中的 final 参数。虽然 Dart 参数默认情况下实际上是 final 的,但显式使用 final 关键字可以使意图更清晰,并防止意外重新赋值。
Final 参数在构造函数和方法中特别有用,您希望确保参数在赋值给实例变量或用于计算之前不被修改。这提高了代码的清晰度和安全性。
$ dart run final_parameters.dart Processing user 123: alice123 Welcome, alice123 Distance: 5.0
Final 与 Const 的区别
此示例比较了 final 和 const,以说明它们在初始化时间、与集合的使用以及对象创建方面的区别。
void main() {
// Final can be initialized at runtime
final currentTime = DateTime.now();
print('Current time: $currentTime');
// Const must be initialized with compile-time constant
const maxAttempts = 3;
print('Max attempts: $maxAttempts');
// This would cause an error:
// const currentTime2 = DateTime.now(); // Error: Not a constant expression
// Final list vs const list
final finalList = [1, 2, 3];
finalList.add(4); // Allowed
print('Final list: $finalList');
const constList = [1, 2, 3];
// constList.add(4); // Error: Cannot add to an unmodifiable list
print('Const list: $constList');
// Final object vs const object
final finalPoint = Point(1, 2);
const constPoint = Point.origin(); // Only if Point has const constructor
print('Final point: $finalPoint');
print('Const point: $constPoint');
}
class Point {
final double x, y;
// Regular constructor
Point(this.x, this.y);
// Const constructor
const Point.origin() : x = 0, y = 0;
@override
String toString() => 'Point($x, $y)';
}
该示例强调了 final 和 const 之间的主要区别。Final 变量可以用运行时值初始化,而 const 需要编译时常量。Final 集合允许内容修改,而 const 集合则完全不可变。
对于对象,const 需要 const 构造函数,并且所有值都必须是编译时常量。Final 对象可以使用常规构造函数和运行时值。当值在编译时已知且不会更改时,请使用 const。
$ dart run final_vs_const.dart Current time: 2025-05-25 14:30:45.123456 Max attempts: 3 Final list: [1, 2, 3, 4] Const list: [1, 2, 3] Final point: Point(1.0, 2.0) Const point: Point(0.0, 0.0)
控制流中的 Final
Final 变量可以在控制流结构中进行条件初始化,只要它们只被赋值一次。此示例显示了不同控制流场景中的 final 变量。
void main() {
final bool isLoggedIn;
final String userRole;
// Simulate authentication
final authResult = authenticate();
if (authResult['success']) {
isLoggedIn = true;
userRole = authResult['role'] ?? 'user';
} else {
isLoggedIn = false;
userRole = 'guest';
}
print('Logged in: $isLoggedIn');
print('Role: $userRole');
// Final in try-catch
late final String apiResponse;
try {
apiResponse = fetchData();
} catch (e) {
apiResponse = 'Error: $e';
}
print('API response: $apiResponse');
}
Map<String, dynamic> authenticate() {
// Simulate authentication result
return {'success': true, 'role': 'admin'};
}
String fetchData() {
// Simulate API call
return 'Data fetched successfully';
}
该示例演示了 final 变量如何在不同的控制流构造中进行初始化,例如 if-else 和 try-catch 块。Dart 编译器确保 final 变量只被赋值一次,从而提供了安全性,同时允许初始化逻辑的灵活性。
authenticate 函数模拟登录过程,返回一个包含 success 标志和用户角色的 map。fetchData 函数模拟 API 调用,返回成功消息或在失败时抛出错误。
$ dart run final_control_flow.dart Logged in: true Role: admin API response: Data fetched successfully
使用 Final 的最佳实践
有效地使用 final 关键字可以提高代码的可读性、安全性和可维护性。以下是处理 Dart 中 final 变量的一些最佳实践:
- 默认使用 final:对于不会重新赋值的变量,优先使用 final 而不是 var。这可以使代码的意图更清晰,并防止意外修改。
- 与 late 结合进行延迟初始化:对于需要在声明后初始化的变量,例如在依赖注入中或当值稍后计算时,请使用 late final。
- 了解集合行为:请记住,final 集合允许修改内容。在适当的情况下,请使用 const 来创建完全不可变的集合。
- 在构造函数中初始化:对于类属性,请在构造函数或声明时初始化 final 实例变量,以确保不变性。
- 使用 final 参数记录意图:在函数和构造函数参数中使用 final 来明确表示它们不会被修改,从而提高代码的清晰度。
来源
Dart 语言之旅:Final 和 Const
Dart Object 类
Dart 中的 final 关键字提供了一种强制执行运行时初始化的变量不变性的强大方法。通过了解它与 const 的区别,并在变量、集合、实例变量、参数和控制流中应用它,开发人员可以编写更安全、更可预测的代码。使用 final 来表示不变性并提高 Dart 应用程序的健壮性。
作者
列出 所有 Dart 教程。