Dart 类与对象
类是面向对象编程的核心概念,它是对现实世界中事物的抽象描述。
对象是类的具体实例。本章介绍 Dart 中类的定义、构造函数、成员变量与方法、getter/setter 和静态成员。
类的定义与实例化
类是创建对象的模板,定义了对象有哪些属性(数据)和行为(方法)。
实例
class User {
// 成员变量(属性)
String name = '';
int age = 0;
// 成员方法(行为)
void introduce() {
print('你好,我是 $name,今年 $age 岁。');
}
bool isAdult() {
return age >= 18;
}
}
void main() {
// 实例化:用 new 关键字(Dart 2.0+ 可省略 new)
var user1 = User();
user1.name = 'runoob';
user1.age = 10;
// 访问成员变量和方法
print('用户名: ${user1.name}');
user1.introduce();
print('是否成年: ${user1.isAdult()}');
// 创建另一个对象,彼此独立
var user2 = User();
user2.name = '小明';
user2.age = 20;
user2.introduce();
}
用户名: runoob 你好,我是 runoob,今年 10 岁。 是否成年: false 你好,我是 小明,今年 20 岁。
Dart 2.0 开始,实例化时 new 关键字是可选的。社区约定是省略 new,保持代码简洁。本教程后续示例都将省略 new。
构造函数
构造函数是在创建对象时自动调用的特殊方法,用于初始化对象的状态。
Dart 提供了多种构造函数形式,非常灵活。
默认构造函数
如果你没有定义构造函数,Dart 会自动提供一个无参的默认构造函数。
普通构造函数
实例
String name;
int age;
// 普通构造函数:与类同名
Student(String name, int age) {
this.name = name;
this.age = age;
}
void printInfo() {
print('学生: $name, 年龄: $age');
}
}
void main() {
var s1 = Student('runoob', 10);
var s2 = Student('小明', 15);
s1.printInfo();
s2.printInfo();
}
学生: runoob, 年龄: 10 学生: 小明, 年龄: 15
语法糖构造函数
当构造函数的参数直接赋值给成员变量时,Dart 提供了简写语法。
实例
String name;
int age;
// 语法糖:this.参数名 自动将参数赋值给同名成员变量
// 这种方式比手动写 this.name = name 更简洁
Student(this.name, this.age);
void printInfo() {
print('RUNOOB 学生: $name, 年龄: $age');
}
}
void main() {
var s = Student('runoob', 10);
s.printInfo();
}
RUNOOB 学生: runoob, 年龄: 10
命名构造函数
一个类可以有多个命名构造函数,用于不同的初始化逻辑。
实例
double x;
double y;
// 默认构造函数
Point(this.x, this.y);
// 命名构造函数:创建原点
Point.origin()
: x = 0,
y = 0;
// 命名构造函数:从单个值创建(x 和 y 相同)
Point.diagonal(double value)
: x = value,
y = value;
@override
String toString() => 'Point($x, $y)';
}
void main() {
var p1 = Point(3, 4);
var p2 = Point.origin();
var p3 = Point.diagonal(5);
print('RUNOOB 坐标: $p1, $p2, $p3');
}
RUNOOB 坐标: Point(3.0, 4.0), Point(0.0, 0.0), Point(5.0, 5.0)
初始化列表
初始化列表在构造函数体执行之前运行,用于初始化 final 字段或进行参数校验。
实例
final String name; // final 字段必须在构造函数中初始化
final int birthYear;
final int age;
// 初始化列表:在冒号后面、函数体前面
// 初始化列表在构造函数体执行之前运行
Person(this.name, this.birthYear)
: age = 2026 - birthYear, // 计算属性
assert(birthYear > 1900, '出生年份不合理'); // 断言校验
void printInfo() {
print('RUNOOB 用户: $name, 出生: $birthYear, 年龄: $age');
}
}
void main() {
var p = Person('runoob', 2016);
p.printInfo();
// 下面这行会触发 assert 失败(开发模式下)
// var p2 = Person('error', 1800);
}
RUNOOB 用户: runoob, 出生: 2016, 年龄: 10
assert 只在开发模式(debug mode)下生效,生产环境(release mode)中会被忽略。它用于在开发阶段尽早发现逻辑错误。
成员变量与方法
成员变量存储对象的状态,成员方法定义对象的行为。
实例
// 私有成员:以下划线开头,仅在本文件中可访问
String _accountNumber;
double _balance = 0;
// 公开成员
String ownerName;
BankAccount(this.ownerName, this._accountNumber);
// 公开方法
void deposit(double amount) {
if (amount > 0) {
_balance += amount;
print('存入 ¥$amount,当前余额: ¥$_balance');
}
}
bool withdraw(double amount) {
if (amount > 0 && amount <= _balance) {
_balance -= amount;
print('取出 ¥$amount,当前余额: ¥$_balance');
return true;
}
print('余额不足,取款失败');
return false;
}
// 私有方法:仅供类内部使用
void _logTransaction(String type, double amount) {
print('[内部日志] $type: ¥$amount');
}
}
void main() {
var account = BankAccount('RUNOOB', '6222-0000-1234');
account.deposit(1000);
account.withdraw(300);
account.withdraw(800); // 余额不足
// 外部无法访问私有成员
// print(account._balance); // 错误:_balance 是私有的
}
存入 ¥1000,当前余额: ¥1000 取出 ¥300,当前余额: ¥700 余额不足,取款失败
Getter 与 Setter
Getter 和 Setter 是对成员变量进行读写操作的特殊方法。
它们让你在访问属性时执行额外的逻辑,同时保持属性访问的简洁语法。
实例
double _radius; // 私有变量,存储半径
Circle(this._radius);
// Getter:获取直径(计算属性)
double get diameter => _radius * 2;
// Getter:获取面积
double get area => 3.14159 * _radius * _radius;
// Getter 和 Setter 配合:对外暴露 radius 属性
double get radius => _radius;
set radius(double value) {
// Setter 中可以添加校验逻辑
if (value <= 0) {
throw ArgumentError('半径必须大于 0');
}
_radius = value;
}
// 只读 Getter:没有对应 Setter
String get info => '圆(半径: $_radius)';
}
void main() {
var circle = Circle(5);
// 使用 Getter(像访问普通属性一样)
print('RUNOOB 圆形: ${circle.info}');
print('直径: ${circle.diameter}');
print('面积: ${circle.area.toStringAsFixed(2)}');
// 使用 Setter
circle.radius = 10;
print('修改后直径: ${circle.diameter}');
// 尝试设置非法值会抛出异常
// circle.radius = -1; // 抛出 ArgumentError
}
RUNOOB 圆形: 圆(半径: 5.0) 直径: 10.0 面积: 78.54 修改后直径: 20.0
Getter 和 Setter 的优势在于:调用者不需要知道 radius 背后是简单字段还是计算属性。如果将来需要给 radius 添加校验逻辑,只需把字段改为 getter/setter,调用代码无需修改。这就是"封装"的好处。
静态成员
静态成员属于类本身,而不是类的某个实例。
所有实例共享同一个静态变量,静态方法不需要创建对象就能调用。
实例
// 静态变量:所有实例共享
static int totalCount = 0;
// 实例变量:每个实例独立
String label;
Counter(this.label) {
// 每次创建实例时,静态计数器加 1
totalCount++;
}
// 静态方法:通过类名调用
static void printTotal() {
print('RUNOOB 总实例数: $totalCount');
}
// 静态方法常用于工具函数
static bool isValidLabel(String label) {
return label.isNotEmpty && label.length <= 20;
}
}
void main() {
// 静态方法:直接通过类名调用,无需创建实例
print('标签是否有效: ${Counter.isValidLabel('test')}');
var c1 = Counter('计数器A');
Counter.printTotal(); // 1
var c2 = Counter('计数器B');
var c3 = Counter('计数器C');
Counter.printTotal(); // 3
// 静态变量:通过类名访问
print('最终计数: ${Counter.totalCount}');
}
标签是否有效: true RUNOOB 总实例数: 1 RUNOOB 总实例数: 3 最终计数: 3
静态方法中不能访问实例成员(因为没有 this)。静态方法适合放工具函数和工厂逻辑,但不要过度使用——静态方法无法被子类重写,过度使用会降低代码的可测试性和灵活性。
