ZetCode

Dart 中的 final 关键字

最后修改于 2025 年 5 月 25 日

本教程将探讨 Dart 中的 final 关键字,它是一种用于声明只能设置一次的变量的修饰符。Final 变量在运行时进行初始化,并为不应在赋值后更改的值提供不变性。

Final 关键字概述

Dart 中的 final 关键字用于声明只能赋值一次的变量。与 const 不同,final 变量在运行时而非编译时进行初始化。这使得它们更加灵活,同时仍然强制执行不变性。

Final 变量必须在使用前进行初始化,可以在声明时初始化,也可以在构造函数中初始化(对于实例变量)。一旦赋值,它们的值就不能更改。Final 通常用于在运行时已知但之后不应更改的值。

功能 Final Const Var
可变性 不可变的 不可变的 可变的
初始化 运行时 编译时 运行时
重新赋值
用例 运行时常量 编译时常量 常规变量

当您需要一个值在初始化后保持不变,但该值仅在运行时才知道时,Final 特别有用。它有助于防止意外修改,使代码更可预测和可维护。

基本 Final 变量

此示例演示了 final 的最简单用法,即声明一个只能设置一次的变量。我们将创建一个 final 变量并尝试修改它。

basic_final.dart
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 如何影响集合的行为。

final_collections.dart
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 通常用于类中的实例变量,以创建不可变属性。这些可以在声明时或在构造函数中初始化。

final_instance.dart
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 结合使用,以推迟初始化,同时仍然确保变量只能设置一次。这对于依赖注入或延迟初始化非常有用。

late_final.dart
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 可用于函数参数,以防止在函数内修改参数。这有助于记录不应更改参数。

final_parameters.dart
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,以说明它们在初始化时间、与集合的使用以及对象创建方面的区别。

final_vs_const.dart
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 变量。

final_control_flow.dart
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 变量的一些最佳实践:

来源

Dart 语言之旅:Final 和 Const
Dart Object 类

Dart 中的 final 关键字提供了一种强制执行运行时初始化的变量不变性的强大方法。通过了解它与 const 的区别,并在变量、集合、实例变量、参数和控制流中应用它,开发人员可以编写更安全、更可预测的代码。使用 final 来表示不变性并提高 Dart 应用程序的健壮性。

作者

我的名字是 Jan Bodnar,我是一名充满热情的程序员,拥有丰富的编程经验。我自 2007 年以来一直在撰写编程文章。迄今为止,我已撰写了 1400 多篇文章和 8 本电子书。我在教授编程方面有十多年的经验。

列出 所有 Dart 教程