现在位置: 首页 > Dart 教程 > 正文

Dart 类与对象

类是面向对象编程的核心概念,它是对现实世界中事物的抽象描述。

对象是类的具体实例。本章介绍 Dart 中类的定义、构造函数、成员变量与方法、getter/setter 和静态成员。


类的定义与实例化

类是创建对象的模板,定义了对象有哪些属性(数据)和行为(方法)。

实例

// 定义一个 User 类
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 会自动提供一个无参的默认构造函数。

普通构造函数

实例

class Student {
  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 提供了简写语法。

实例

class Student {
  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

命名构造函数

一个类可以有多个命名构造函数,用于不同的初始化逻辑。

实例

class Point {
  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 字段或进行参数校验。

实例

class Person {
  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)中会被忽略。它用于在开发阶段尽早发现逻辑错误。


成员变量与方法

成员变量存储对象的状态,成员方法定义对象的行为。

实例

class BankAccount {
  // 私有成员:以下划线开头,仅在本文件中可访问
  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 是对成员变量进行读写操作的特殊方法。

它们让你在访问属性时执行额外的逻辑,同时保持属性访问的简洁语法。

实例

class Circle {
  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,调用代码无需修改。这就是"封装"的好处。


静态成员

静态成员属于类本身,而不是类的某个实例。

所有实例共享同一个静态变量,静态方法不需要创建对象就能调用。

实例

class Counter {
  // 静态变量:所有实例共享
  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)。静态方法适合放工具函数和工厂逻辑,但不要过度使用——静态方法无法被子类重写,过度使用会降低代码的可测试性和灵活性。