Classes

Classes

Dart 是一种面向对象的语言,拥有类和基于 mixin 的继承。每个对象都是一个类的实例,并且除了 Null 之外的所有类都派生自 Object基于 mixin 的继承意味着尽管每个类(除了顶级类 Object?)都只有一个超类,但一个类的代码体可以在多个类层次结构中被复用。扩展方法是一种在不修改类或创建子类的情况下为类添加功能的方式。类修饰符允许你控制库如何对一个类进行子类型化。

使用类成员

对象拥有由函数和数据组成的成员(分别为方法实例变量)。当你调用一个方法时,你是在一个对象上调用 (invoke) 它:该方法可以访问该对象的函数和数据。

使用点号 (.) 来引用一个实例变量或方法:

1
2
3
4
5
6
7
var p = Point(2, 2);

// 获取 y 的值。
assert(p.y == 2);

// 在 p 上调用 distanceTo() 方法。
double distance = p.distanceTo(Point(4, 4));

当最左边的操作数可能为 null 时,使用 ?. 代替 . 来避免产生异常:

1
2
// 如果 p 不为 null,则将其 y 值赋给一个变量。
var a = p?.y;

使用构造函数

你可以使用构造函数 (constructor) 来创建一个对象。构造函数的名称可以是 ClassNameClassName.identifier。例如,下面的代码使用 Point()Point.fromJson() 构造函数来创建 Point 对象:

1
2
var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});

下面的代码效果相同,但在构造函数名前使用了可选的 new 关键字:

1
2
var p1 = new Point(2, 2);
var p2 = new Point.fromJson({'x': 1, 'y': 2});

有些类提供常量构造函数 (constant constructors)。要使用常量构造函数创建一个编译时常量,请在构造函数名前加上 const 关键字:

1
var p = const ImmutablePoint(2, 2);

构造两个相同的编译时常量会产生一个单一的、规范的实例:

1
2
3
4
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);

assert(identical(a, b)); // 它们是同一个实例!

常量上下文中,你可以省略构造函数或字面量之前的 const。例如,看这段创建了一个 const map 的代码:

1
2
3
4
5
// 这里有很多 const 关键字。
const pointAndLine = const {
'point': const [const ImmutablePoint(0, 0)],
'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};

你可以省略掉除了第一个 const 关键字之外的所有 const

1
2
3
4
5
// 只有一个 const,它建立了常量上下文。
const pointAndLine = {
'point': [ImmutablePoint(0, 0)],
'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};

如果一个常量构造函数在常量上下文之外且未使用 const 调用,它会创建一个非常量对象:

1
2
3
4
var a = const ImmutablePoint(1, 1); // 创建一个常量
var b = ImmutablePoint(1, 1); // 不会创建一个常量

assert(!identical(a, b)); // 不是同一个实例!

获取对象的类型

要在运行时获取一个对象的类型,你可以使用 ObjectruntimeType 属性,它会返回一个 Type 对象。

1
print('The type of a is ${a.runtimeType}');

警告
使用类型测试操作符而不是 runtimeType 来测试对象的类型。在生产环境中,测试 object is Type 比测试 object.runtimeType == Type 更稳定。

到目前为止,你已经了解了如何使用类。本节的其余部分将展示如何实现类。

实例变量

下面是你如何声明实例变量的方式:

1
2
3
4
5
class Point {
double? x; // 声明实例变量 x,初始值为 null。
double? y; // 声明 y,初始值为 null。
double z = 0; // 声明 z,初始值为 0。
}

一个使用可空类型声明但未初始化的实例变量,其值为 null。不可空的实例变量必须在声明时初始化。

所有实例变量都会生成一个隐式的 getter 方法。非 final 的实例变量和没有初始化器的 late final 实例变量还会生成一个隐式的 setter 方法。详情请查阅 Getters and setters

1
2
3
4
5
6
7
8
9
10
11
class Point {
double? x; // 声明实例变量 x,初始值为 null。
double? y; // 声明 y,初始值为 null。
}

void main() {
var point = Point();
point.x = 4; // 使用 x 的 setter 方法。
assert(point.x == 4); // 使用 x 的 getter 方法。
assert(point.y == null); // 值默认为 null。
}

在声明处初始化一个非 late 的实例变量,会在实例创建时、在构造函数及其初始化列表执行之前设置该值。因此,一个非 late 实例变量的初始化表达式(= 之后的部分)不能访问 this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
double initialX = 1.5;

class Point {
// OK,可以访问不依赖 `this` 的声明:
double? x = initialX;

// 错误,不能在非 `late` 的初始化器中访问 `this`:
// double? y = this.x;

// OK,可以在 `late` 初始化器中访问 `this`:
late double? z = this.x;

// OK,`this.x` 和 `this.y` 是参数声明,不是表达式:
Point(this.x, this.y);
}

实例变量可以是 final 的,这种情况下它们必须且只能被设置一次。请在声明时、使用构造函数参数、或使用构造函数的初始化列表来初始化 final、非 late 的实例变量:

1
2
3
4
5
6
7
class ProfileMark {
final String name;
final DateTime start = DateTime.now();

ProfileMark(this.name);
ProfileMark.unnamed() : name = '';
}

如果你需要在构造函数体开始之后才为一个 final 实例变量赋值,你可以使用以下方法之一:

  • 使用一个工厂构造函数
  • 使用 late final,但要小心:一个没有初始化器的 late final 变量会向 API 中添加一个 setter。

隐式接口

每个类都隐式地定义了一个接口,该接口包含了该类的所有实例成员以及它所实现的任何接口的成员。如果你想创建一个支持类 B 的 API 但不继承 B 的实现的类 A,那么类 A 应该实现 (implement) B 接口。

一个类通过在 implements 子句中声明一个或多个接口,然后提供这些接口所需的 API,来实现这些接口。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 一个 Person。隐式接口包含了 greet()。
class Person {
// 在接口中,但仅在此库中可见。
final String _name;

// 不在接口中,因为这是一个构造函数。
Person(this._name);

// 在接口中。
String greet(String who) => 'Hello, $who. I am $_name.';
}

// Person 接口的一个实现。
class Impostor implements Person {
String get _name => '';

String greet(String who) => 'Hi $who. Do you know who I am?';
}

String greetBob(Person person) => person.greet('Bob');

void main() {
print(greetBob(Person('Kathy')));
print(greetBob(Impostor()));
}

下面是一个指定一个类实现多个接口的例子:

1
2
3
class Point implements Comparable, Location {
// ...
}

类变量和方法

使用 static 关键字来实现类级别的变量和方法。

静态变量

静态变量(类变量)对于类级别的状态和常量很有用:

1
2
3
4
5
6
7
8
class Queue {
static const initialCapacity = 16;
// ···
}

void main() {
assert(Queue.initialCapacity == 16);
}

静态变量在使用前不会被初始化。

注意
本页遵循风格指南的建议,对常量名优先使用小驼峰命名法 (lowerCamelCase)。

静态方法

静态方法(类方法)不操作于实例上,因此无法访问 this。然而,它们可以访问静态变量。如下例所示,你可以直接在类上调用静态方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import 'dart:math';

class Point {
double x, y;
Point(this.x, this.y);

static double distanceBetween(Point a, Point b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
}

void main() {
var a = Point(2, 2);
var b = Point(4, 4);
var distance = Point.distanceBetween(a, b);
assert(2.8 < distance && distance < 2.9);
print(distance);
}

注意
对于通用或广泛使用的工具和功能,考虑使用顶级函数,而不是静态方法。

你可以将静态方法用作编译时常量。例如,你可以将一个静态方法作为参数传递给一个常量构造函数。

作者

wuhunyu

发布于

2025-09-09

更新于

2025-09-11

许可协议