Dart: Dart 变量与数据类型 — 五种声明方式与内置类型
变量是数据的容器,类型是容器的形状 — 选对容器,代码才安全。
1. 你将学到
- var / final / const / late / dynamic 五种声明方式对比
- 内置类型:int / double / String / bool / num
- 类型推断与显式标注的最佳实践
- String 插值与多行字符串(三引号 + r 前缀)
- Bob 场景:DataPipeline 中的配置变量设计
2. 一个开发者的真实故事
(1) 痛点:随意声明变量导致运行时崩溃
Bob 在开发 DataPipeline 的早期版本时,所有变量都用 var 声明,还经常中途修改变量类型。一次重构中,他误将 int orderCount 改为 String orderCount,编译没报错(dynamic 类型),但运行时 orderCount * price 抛出异常,导致处理 500,000 条订单的批处理任务中断,报表延迟 4 小时交付。
(2) 类型安全的解法
Dart 提供了从宽松到严格的一系列声明方式:const(编译时常量)→ final(运行时常量)→ var(推断类型)→ late(延迟初始化)→ dynamic(动态类型)。合理选用能让编译器帮你找 bug。
// Best practice for DataPipeline configuration
const int maxRecords = 1000000; // Compile-time constant
final String pipelineName; // Runtime constant (set once)
var processedCount = 0; // Inferred as int, mutable
late String outputPath; // Initialized later
// dynamic should be avoided if possible
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
(3) 收益
- 编译器在编译期发现类型错误,而不是运行时崩溃
- const 变量让编译器优化性能
- final 防止意外修改,代码意图更清晰
3. 五种声明方式
(1) 声明方式决策树
graph LR
subgraph Declarations
A[var] --> B[Type inference]
C[final] --> D[Runtime constant]
E[const] --> F[Compile-time constant]
G[late] --> H[Lazy initialization]
I[dynamic] --> J[Dynamic type]
end
subgraph Built-in Types
K[int] --- L[double]
M[String] --- N[bool]
end
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:var — 类型推断
void main() {
var name = 'DataPipeline'; // Inferred as String
var count = 1000; // Inferred as int
var rate = 0.08; // Inferred as double
// Can reassign same type
name = 'Analytics'; // OK - still String
count = 2000; // OK - still int
// Cannot change type
// count = 'two thousand'; // Compile error!
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:final — 运行时常量
void main() {
// Set once at runtime
final DateTime startTime = DateTime.now();
final String configPath = '/etc/datapipeline/config.yaml';
// Cannot reassign
// configPath = '/other/path'; // Compile error!
// final with collection - contents can change
final List``<String>`` sources = ['orders.csv', 'products.csv'];
sources.add('customers.csv'); // OK - modifying contents
// sources = ['new_list']; // Error - cannot reassign reference
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:const — 编译时常量
void main() {
// Must be known at compile time
const int maxRecords = 1000000;
const double taxRate = 0.08;
const String version = '1.0.0';
// const collections are deeply immutable
const List``<String>`` formats = ['json', 'csv', 'html'];
// formats.add('xml'); // Error! Cannot modify const list
// const constructor
const config = PipelineConfig(batchSize: 10000);
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:late — 延迟初始化
class DataPipeline {
late String outputPath; // Will be set later
void configure(String path) {
outputPath = path;
}
late final int maxRecords = _loadMaxRecords(); // Computed on first access
int _loadMaxRecords() {
print('Loading max records config...');
return 1000000;
}
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:dynamic — 动态类型
void main() {
dynamic value = 'hello'; // String
value = 42; // OK - now int
value = [1, 2, 3]; // OK - now List
// No compile-time type checking - risky!
// value.nonExistentMethod(); // No compile error, runtime crash
// Prefer Object? over dynamic when you need flexibility
Object? safeValue = 'test';
// safeValue.nonExistentMethod(); // Compile error - safer
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
(2) 五种声明方式对比
| 声明方式 | 类型 | 可变 | 初始化时机 | 安全性 | 使用场景 |
|---|---|---|---|---|---|
const |
编译时确定 | 不可变 | 声明时 | 最高 | 配置常量、枚举值 |
final |
运行时确定 | 不可变 | 声明时或构造函数 | 高 | 运行时配置、注入值 |
var |
推断 | 可变 | 声明时 | 中 | 局部变量、计数器 |
late |
显式标注 | 可变 | 延迟 | 中 | 延迟初始化字段 |
dynamic |
运行时 | 可变 | 任意 | 低 | JSON 解析、互操作 |
4. 内置类型详解
(1) 数值类型
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:int 与 double
void main() {
int orderCount = 1500;
double totalAmount = 52500.75;
num genericNum = 42; // num is supertype of int and double
genericNum = 3.14; // OK - num accepts both
// Arithmetic operations
double avgOrderValue = totalAmount / orderCount;
int roundedDown = totalAmount.toInt();
// Convenient methods
print(totalAmount.toStringAsFixed(2)); // 52500.75
print(orderCount.isEven); // false (1500 is even, true)
print(orderCount.clamp(0, 1000)); // 1000 (clamped to max)
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
| 类型 | 范围 | 用途 |
|---|---|---|
int |
64-bit (JS 限制时不同) | 计数、索引 |
double |
64-bit IEEE 754 | 金额、比例 |
num |
int + double 的父类型 | 通用数值 |
(2) String 类型
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:String 插值与操作
void main() {
// String interpolation
String product = 'DataPipeline';
int users = 5000;
print('$product has $users users'); // Simple interpolation
print('Revenue: ${users * 99} USD'); // Expression interpolation
print('Average: ${(125000 / users).toStringAsFixed(2)} USD');
// Multi-line strings
String description = '''
DataPipeline - E-Commerce Analytics Tool
Processes up to 1,000,000 orders per batch
Supports JSON, CSV, and HTML output formats
''';
// Raw strings (no escape processing)
String path = r'C:\Users\Bob\data\orders.csv';
String regex = r'\d{4}-\d{2}-\d{2}'; // Date pattern
// Useful methods
String raw = ' Hello, World! ';
print(raw.trim()); // 'Hello, World!'
print(raw.toUpperCase()); // ' HELLO, WORLD! '
print('USD 1,500.00'.replaceAll(',', '')); // 'USD 1500.00'
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
| 特性 | 语法 | 示例 |
|---|---|---|
| 简单插值 | $variable |
'$name' |
| 表达式插值 | ${expr} |
'${a + b}' |
| 多行字符串 | '''...''' |
三引号 |
| 原始字符串 | r'...' |
r'\n' 保持原样 |
(3) bool 类型
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:布尔值与条件
void main() {
bool isProduction = true;
bool isDebug = false;
// Dart requires explicit bool in conditions
int count = 0;
// if (count) {} // Error! Must be bool
if (count > 0) { // OK - explicit comparison
print('Has records');
}
// Logical operators
bool shouldProcess = isProduction && !isDebug;
bool hasData = count > 0 || isProduction;
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
5. 类型推断与显式标注
(1) 最佳实践
// Good - let the compiler infer for local variables
var name = 'DataPipeline'; // Inferred String
var count = 100; // Inferred int
var items = ``<String>``[]; // Explicit generic, inferred List
// Good - explicit type for public API
String get displayName => name;
int get maxCapacity => 1000000;
// Good - final for values that don't change
final startTime = DateTime.now();
// Avoid - unnecessary explicit type on locals
// String name = 'DataPipeline'; // Redundant
// int count = 100; // Redundant
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
| 场景 | 推荐 | 原因 |
|---|---|---|
| 局部变量 | var / final |
减少冗余,推断足够 |
| 公开 API | 显式标注 | 文档清晰,接口稳定 |
| 集合泛型 | 显式泛型 | 避免推断为 `List`` |
| 构造函数参数 | 显式标注 | 契约清晰 |
6. Bob 场景:DataPipeline 配置变量
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:配置变量设计
// DataPipeline configuration constants
const String appName = 'DataPipeline';
const int defaultBatchSize = 10000;
const double defaultTaxRate = 0.08;
// Runtime configuration
class PipelineConfig {
final String version;
final int batchSize;
final String outputFormat;
PipelineConfig({
this.version = '1.0.0',
this.batchSize = defaultBatchSize,
this.outputFormat = 'json',
});
@override
String toString() =>
'$appName v$version | Batch: $batchSize | Format: $outputFormat';
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
7. 完整示例:DataPipeline 变量声明实战
// ============================================
// DataPipeline Variable Declaration Demo
// Shows proper use of all declaration types
// ============================================
// Compile-time constants - never change across runs
const String appName = 'DataPipeline';
const int maxRecords = 1000000;
const double taxRate = 0.08;
const List``<String>`` supportedFormats = ['json', 'csv', 'html'];
class Order {
final String id; // Runtime constant after construction
final double amount; // Runtime constant after construction
late String status; // Will be set by processing logic
Order({required this.id, required this.amount});
double get tax => amount * taxRate;
double get total => amount + tax;
String get formattedAmount =>
'\$${amount.toStringAsFixed(2)} USD';
}
class DataPipeline {
final String name;
var processedCount = 0; // Mutable counter
late final DateTime startTime; // Set on first run
DataPipeline({required this.name});
void start() {
startTime = DateTime.now();
print('$appName started at $startTime');
}
void processOrder(Order order) {
processedCount++;
order.status = 'processed';
print('Order ${order.id}: ${order.formattedAmount} '
'(tax: \$${order.tax.toStringAsFixed(2)} USD)');
}
void printSummary() {
print('\n=== Summary ===');
print('Pipeline: $name');
print('Processed: $processedCount orders');
print('Capacity: $processedCount / $maxRecords');
}
}
void main() {
final pipeline = DataPipeline(name: 'E-Commerce Analytics');
pipeline.start();
final orders = [
Order(id: 'ORD-001', amount: 1500.00),
Order(id: 'ORD-002', amount: 3250.50),
Order(id: 'ORD-003', amount: 890.25),
];
for (final order in orders) {
pipeline.processOrder(order);
}
pipeline.printSummary();
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
输出:
DataPipeline started at 2024-01-15 10:30:00.000
Order ORD-001: $1500.00 USD (tax: $120.00 USD)
Order ORD-002: $3250.50 USD (tax: $260.04 USD)
Order ORD-003: $890.25 USD (tax: $71.22 USD)
=== Summary ===
Pipeline: E-Commerce Analytics
Processed: 3 orders
Capacity: 3 / 1000000
❓ 常见问题
Q:var 和 final 该用哪个? A:如果变量不会重新赋值,优先用 final。如果需要修改,用 var。Dart 官方 lint 规则 prefer_final_locals 推荐优先使用 final。
Q:const 和 final 的区别是什么? A:const 是编译时常量,值在编译时必须确定;final 是运行时常量,只能赋值一次但值可以在运行时计算。DateTime.now() 不能用 const。
Q:什么时候用 late? A:当变量无法在声明时初始化,但你能保证在使用前一定会初始化时用 late。常见场景:依赖注入、构造后配置。误用会导致运行时 LateInitializationError。
Q:dynamic 和 Object? 有什么区别? A:dynamic 关闭所有类型检查,编译器不校验任何方法调用;Object? 保留类型检查,只能调用 Object 的方法。优先用 Object?。
Q:Dart 的 int 在 Web 和 VM 上一样吗? A:不完全一样。Dart VM 上 int 是 64 位整数;编译为 JS 时,int 受 JS Number 限制(53 位精度)。大数运算需注意。
Q:String 可以用单引号还是双引号? A:都可以,效果完全一样。Dart 官方风格指南推荐单引号,除非字符串内包含单引号时才用双引号。
Q:num 类型什么时候用? A:当你需要同时接受 int 和 double 的参数或变量时用 num。num 是 int 和 double 的父类型,支持基本算术运算。
📖 小节
- 五种声明方式从严格到宽松:const → final → var → late → dynamic
- const 编译时确定、final 运行时确定但只赋值一次、var 可变且推断类型
- 内置类型:int(整数)、double(浮点)、String(字符串+插值)、bool(布尔)
- 局部变量用 var/final 推断,公开 API 显式标注类型
- DataPipeline 用 const 定义配置常量,final 定义运行时不变的值
📝 作业
- 基础题(难度⭐):声明以下变量:一个 const 的应用名称、一个 final 的当前时间、一个 var 的计数器,分别打印它们的值和类型。
- 进阶题(难度⭐⭐):写一个函数,接受 num 类型参数,返回该数值的 USD 格式字符串(如
formatUSD(1500.5)返回"$1,500.50 USD")。注意 int 和 double 的处理。 - 挑战题(难度⭐⭐⭐):设计一个
Config类,用 late 延迟初始化数据库连接字符串,用 const 定义默认值,用 final 保存从环境变量读取的配置,展示三种声明方式的协作。