Classes
Using constructors
使用构造函数创建对象。 构造函数名称可以是 ClassName 或 ClassName.identifier。 例如,以下代码使用 Point() 和 Point.fromJson() 构造函数创建 Point 对象:
class Point {
var x;
var y;
Point(int this.x, int this.y);
Point.fromJson(Map data)
: x = data['x'],
y = data['y'];
}
void main() {
var p1 = Point(2, 2);
print(p1.x);
print(p1.y);
var p2 = Point.fromJson({'x': 1, 'y': 2});
print(p2.x);
print(p2.y);
}
Log
2
2
1
2
以下代码具有相同的效果,但在构造函数名称之前使用了可选的 new 关键字:
class Point {
var x;
var y;
Point(int this.x, int this.y);
Point.fromJson(Map data)
: x = data['x'],
y = data['y'];
}
// void main() {
// var p1 = Point(2, 2);
// print(p1.x);
// print(p1.y);
// var p2 = Point.fromJson({'x': 1, 'y': 2});
// print(p2.x);
// print(p2.y);
// }
void main() {
var p1 = new Point(2, 2);
print(p1.x);
print(p1.y);
var p2 = new Point.fromJson({'x': 1, 'y': 2});
print(p2.x);
print(p2.y);
}
Log
2
2
1
2
一些类提供常量构造函数。 要使用常量构造函数创建编译时常量,请将 const 关键字放在构造函数名称之前:
class ImmutablePoint {
static const ImmutablePoint origin = ImmutablePoint(0, 0);
final double x, y;
const ImmutablePoint(this.x, this.y);
}
void main() {
var ponit = const ImmutablePoint(2, 2);
print(ponit.x);
print(ponit.y);
}
Log
2.0
2.0
构造两个相同的编译时常量会产生一个单一的规范实例:
class ImmutablePoint {
static const ImmutablePoint origin = ImmutablePoint(0, 0);
final double x, y;
const ImmutablePoint(this.x, this.y);
}
void main() {
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);
print(a == b);
}
Log
true
在常量上下文中,您可以省略构造函数或文字前的常量。 例如,看看这段代码,它创建了一个 const 映射:
class ImmutablePoint {
static const ImmutablePoint origin = ImmutablePoint(0, 0);
final double x, y;
const ImmutablePoint(this.x, this.y);
}
void main() {
// Lots of const keywords here.
const pointAndLine = const {
'point': const [const ImmutablePoint(0, 0)],
'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};
print(pointAndLine['point']);
print(pointAndLine['line']);
}
Log
[Instance of 'ImmutablePoint']
[Instance of 'ImmutablePoint', Instance of 'ImmutablePoint']
可以省略除第一次使用 const 关键字以外的所有内容:
class ImmutablePoint {
static const ImmutablePoint origin = ImmutablePoint(0, 0);
final double x, y;
const ImmutablePoint(this.x, this.y);
}
void main() {
// Only one const, which establishes the constant context.
const pointAndLine = {
'point': [ImmutablePoint(0, 0)],
'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};
print(pointAndLine['point']);
print(pointAndLine['line']);
}
Log
[Instance of 'ImmutablePoint']
[Instance of 'ImmutablePoint', Instance of 'ImmutablePoint']
如果常量构造函数在常量上下文之外并且在没有 const 的情况下被调用,它会创建一个非常量对象:
class ImmutablePoint {
static const ImmutablePoint origin = ImmutablePoint(0, 0);
final double x, y;
const ImmutablePoint(this.x, this.y);
}
void main() {
var a = const ImmutablePoint(1, 1); // Creates a constant
var b = ImmutablePoint(1, 1); // Does NOT create a constant
print(a == b); // NOT the same instance!
}
Log
false
Getting an object’s type
要在运行时获取对象的类型,您可以使用 Object 属性 runtimeType,它返回一个 Type 对象。
class ImmutablePoint {
static const ImmutablePoint origin = ImmutablePoint(0, 0);
final double x, y;
const ImmutablePoint(this.x, this.y);
}
void main() {
var a = const ImmutablePoint(1, 1); // Creates a constant
var b = ImmutablePoint(1, 1); // Does NOT create a constant
print(a.runtimeType);
print(b.runtimeType);
}
Log
ImmutablePoint
ImmutablePoint
使用类型测试运算符而不是 runtimeType 来测试对象的类型。 在生产环境中,测试对象是 Type 比 test object.runtimeType == Type 更稳定。
Instance variables
以下是声明实例变量的方式:
class Point {
double? x; // Declare instance variable x, initially null.
double? y; // Declare y, initially null.
double z = 0; // Declare z, initially 0.
}
所有未初始化的实例变量的值为 null。
所有实例变量都会生成一个隐式的 getter 方法。 非最终实例变量和没有初始值设定项的晚期最终实例变量也会生成隐式设置方法。 有关详细信息,请参阅 Getters 和 setter。
如果你在声明它的地方初始化一个非晚期实例变量,则在创建实例时设置该值,这是在构造函数及其初始化列表执行之前。 因此,非晚期实例变量初始值设定项无法访问 this。
class Point {
double? x; // Declare instance variable x, initially null.
double? y; // Declare y, initially null.
}
void main() {
var point = Point();
point.x = 4; // Use the setter method for x.
print(point.x == 4); // Use the getter method for x.
print(point.y == null); // Values default to null.
}
Log
true
true
实例变量可以是final的,在这种情况下,它们只能被设置一次。 使用构造函数参数或使用构造函数的初始化列表在声明时初始化final、non-late实例变量:
class ProfileMark {
final String name;
final DateTime start = DateTime.now();
ProfileMark(this.name);
ProfileMark.unnamed() : name = '';
}
void main() {
var profile = ProfileMark("ZY");
print(profile.name);
print(profile.start);
var profile2 = ProfileMark.unnamed();
print(profile2.name);
print(profile2.start);
}
Log
ZY
2023-02-08 17:07:02.361116
2023-02-08 17:07:02.364109
如果需要在构造函数体启动后为final实例变量赋值,可以使用以下方法之一:
- Use a factory constructor.
- Use late final, but be careful: a late final without an initializer adds a setter to the API.
Methods
方法是为对象提供行为的函数。
实例方法
对象的实例方法可以访问实例变量和 this。 以下示例中的 distanceTo() 方法是实例方法的示例:
import 'dart:math';
class Point {
final double x;
final double y;
Point(this.x, this.y);
double distanceTo(Point other) {
var dx = x - other.x;
var dy = y - other.y;
return sqrt(dx * dx + dy * dy);
}
}
void main() {
var p1 = Point(7, 8);
var p2 = Point(3, 5);
var value = p1.distanceTo(p2);
print(value);
}
Log
5.0
Operators
运算符是具有特殊名称的实例方法。 Dart 允许使用以下名称定义运算符:
注意:您可能已经注意到,某些运算符(如 !=)不在名称列表中。 那是因为它们只是语法糖。 例如,表达式 e1 != e2 是 !(e1 == e2) 的语法糖。
使用内置标识符运算符标识运算符声明。 以下示例定义了向量加法 (+)、减法 (-) 和相等 (==):
class Vector {
final int x, y;
Vector(this.x, this.y);
Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
Vector operator -(Vector v) => Vector(x - v.x, y - v.y);
@override
bool operator ==(Object other) =>
other is Vector && x == other.x && y == other.y;
@override
int get hashCode => Object.hash(x, y);
}
void main() {
final v = Vector(2, 3);
final w = Vector(2, 2);
print(v + w == Vector(4, 5));
print(v - w == Vector(0, 1));
}
Log
true
true
getter 和 setter
getter 和 setter 是提供对对象属性的读写访问的特殊方法。 回想一下,每个实例变量都有一个隐式的 getter,如果合适的话还有一个 setter。 您可以通过使用 get 和 set 关键字实现 getter 和 setter 来创建其他属性:
class Rectangle {
double left, top, width, height;
Rectangle(this.left, this.top, this.width, this.height);
// Define two calculated properties: right and bottom.
double get right => left + width;
set right(double value) => left = value - width;
double get bottom => top + height;
set bottom(double value) => top = value - height;
}
void main() {
var rect = Rectangle(3, 4, 20, 15);
print(rect.left == 3);
rect.right = 12;
print(rect.left == -8);
}
Log
true
true
使用 getter 和 setter,可以从实例变量开始,然后用方法包装它们,所有这些都无需更改客户端代码。
注意:诸如增量 (++) 之类的运算符以预期的方式工作,无论是否显式定义了 getter。 为避免任何意外的副作用,运算符只调用一次 getter,将其值保存在临时变量中。
Abstract methods
实例、getter 和 setter 方法可以是抽象的,定义一个接口,但将其实现留给其他类。 抽象方法只能存在于抽象类中。
要使方法抽象,请使用分号 (;) 而不是方法主体:
abstract class Doer {
// Define instance variables and methods...
void doSomething(); // Define an abstract method.
}
class EffectiveDoer extends Doer {
void doSomething() {
// Provide an implementation, so the method is not abstract here...
}
}
Abstract classes
使用 abstract 修饰符定义一个抽象类——一个不能被实例化的类。 抽象类对于定义接口很有用,通常带有一些实现。 如果您希望您的抽象类看起来是可实例化的,请定义一个工厂构造函数。
抽象类通常有抽象方法。 下面是一个声明具有抽象方法的抽象类的示例:
// This class is declared abstract and thus
// can't be instantiated.
abstract class AbstractContainer {
// Define constructors, fields, methods...
void updateChildren(); // Abstract method.
}
Implicit interfaces
每个类都隐式定义了一个接口,其中包含该类的所有实例成员以及它实现的任何接口。 如果要创建一个支持类 B 的 API 而不继承类 B 的实现的类 A,类 A 应该实现 B 接口。
一个类通过在 implements 子句中声明它们然后提供接口所需的 API 来实现一个或多个接口。 例如:
// A person. The implicit interface contains greet().
class Person {
// In the interface, but visible only in this library.
final String _name;
// Not in the interface, since this is a constructor.
Person(this._name);
// In the interface.
String greet(String who) => 'Hello, $who. I am $_name.';
}
// An implementation of the Person interface.
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()));
}
Log
Hello, Bob. I am Kathy.
Hi Bob. Do you know who I am?
下面是指定一个类实现多个接口
class Point implements Comparable, Location {...}
Extending a class
使用 extends 创建子类,使用 super 引用超类:
class Television {
void turnOn() {
_illuminateDisplay();
_activateIrSensor();
}
// ···
}
class SmartTelevision extends Television {
void turnOn() {
super.turnOn();
_bootNetworkInterface();
_initializeMemory();
_upgradeApps();
}
// ···
}
Overriding members
子类可以覆盖实例方法(包括运算符)、getter 和 setter。 您可以使用 @override 注释来指示您有意覆盖成员:
class Television {
// ···
set contrast(int value) {...}
}
class SmartTelevision extends Television {
@override
set contrast(num value) {...}
// ···
}
覆盖方法声明必须以多种方式匹配它覆盖的方法(或多个方法):
- 返回类型必须与重写方法的返回类型相同(或子类型)。
- 参数类型必须与重写方法的参数类型相同(或超类型)。 在前面的示例中,SmartTelevision 的对比度设置器将参数类型从 int 更改为超类型 num。
- 如果重写方法接受 n 个位置参数,则重写方法也必须接受 n 个位置参数。
- 泛型方法不能覆盖非泛型方法,非泛型方法也不能覆盖泛型方法。
有时可能希望缩小方法参数或实例变量的类型。 这违反了正常规则,它类似于向下转换,因为它会在运行时导致类型错误。 尽管如此,如果代码可以保证不会发生类型错误,则缩小类型是可能的。 在这种情况下,您可以在参数声明中使用协变关键字。 有关详细信息,请参阅 Dart 语言规范。
警告:如果你覆盖了 ==,你也应该覆盖 Object 的 hashCode getter。 有关覆盖 == 和 hashCode 的示例,请参阅实施映射键。
noSuchMethod()
使用不存在的方法或实例变量时检测或做出反应,可以覆盖 noSuchMethod():
class A {
// Unless you override noSuchMethod, using a
// non-existent member results in a NoSuchMethodError.
@override
void noSuchMethod(Invocation invocation) {
print('You tried to use a non-existent member: '
'${invocation.memberName}');
}
}
要在代码尝试使用不存在的方法或实例变量时检测或做出反应,可以重写 noSuchMethod():不能调用未实现的方法,除非满足以下条件之一:
- 接收器具有静态类型动态。
- 接收器有一个定义未实现方法的静态类型(抽象是可以的),接收器的动态类型有一个 noSuchMethod() 的实现,它不同于类 Object 中的实现。
有关详细信息,请参阅非正式的 noSuchMethod 转发规范。
Extension methods
扩展方法是一种向现有库添加功能的方法。 可能会在不知不觉中使用扩展方法。 例如,当在 IDE 中使用代码完成时,它会建议扩展方法和常规方法。
这是在 string_apis.dart 中定义的名为 parseInt() 的 String 上使用扩展方法的示例:
import 'string_apis.dart';
...
print('42'.padLeft(5)); // Use a String method.
print('42'.parseInt()); // Use an extension method.
Enumerated types
枚举类型,通常称为枚举或枚举,是一种特殊的类,用于表示固定数量的常量值。
注意:所有枚举都会自动扩展 Enum 类。 它们也是密封的,这意味着它们不能被子类化、实现、混合或以其他方式显式实例化。
抽象类和混合可以显式实现或扩展枚举,但除非它们随后由枚举声明实现或混合到枚举声明中,否则没有对象可以实际实现该类或混合的类型。
声明简单枚举
要声明一个简单的枚举类型,请使用 enum 关键字并列出要枚举的值:
enum Color { red, green, blue }
声明增强的枚举
Dart 还允许枚举声明来声明具有字段、方法和 const 构造函数的类,这些类仅限于固定数量的已知常量实例。
要声明增强型枚举,请遵循与普通类类似的语法,但有一些额外要求:
- 实例变量必须是 final 的,包括那些由 mixins 添加的。
- 所有生成构造函数都必须是常量。
- 工厂构造函数只能返回一个固定的已知枚举实例。
- 没有其他类可以扩展,因为 Enum 是自动扩展的。
- 索引、hashCode、相等运算符 == 不能被覆盖。
- 不能在枚举中声明名为 values 的成员,因为它会与自动生成的静态值 getter 冲突。
- 枚举的所有实例必须在声明的开头声明,并且必须至少声明一个实例。
下面是一个示例,它声明了一个具有多个实例、实例变量、一个 getter 和一个已实现接口的增强型枚举:
enum Vehicle implements Comparable<Vehicle> {
car(tires: 4, passengers: 5, carbonPerKilometer: 400),
bus(tires: 6, passengers: 50, carbonPerKilometer: 800),
bicycle(tires: 2, passengers: 1, carbonPerKilometer: 0);
const Vehicle({
required this.tires,
required this.passengers,
required this.carbonPerKilometer,
});
final int tires;
final int passengers;
final int carbonPerKilometer;
int get carbonFootprint => (carbonPerKilometer / passengers).round();
@override
int compareTo(Vehicle other) => carbonFootprint - other.carbonFootprint;
}
使用枚举
像访问任何其他静态变量一样访问枚举值:
final favoriteColor = Color.blue;
if (favoriteColor == Color.blue) {
print('Your favorite color is blue!');
}
枚举中的每个值都有一个索引获取器,它返回枚举声明中值从零开始的位置。 例如,第一个值的索引为 0,第二个值的索引为 1。
assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);
要获取所有枚举值的列表,请使用枚举的值常量。
List<Color> colors = Color.values;
assert(colors[2] == Color.blue);
可以在 switch 语句中使用枚举:
var aColor = Color.blue;
switch (aColor) {
case Color.red:
print('Red as roses!');
break;
case Color.green:
print('Green as grass!');
break;
default: // Without this, you see a WARNING.
print(aColor); // 'Color.blue'
}
访问枚举值的名称,例如 Color.blue 中的“blue”,请使用 .name 属性:
print(Color.blue.name); // 'blue'
Adding features to a class: mixins
Mixins 是一种在多个类层次结构中重用类代码的方法。完成“多继承”的职能。如果A类混入了B类,那么A就可以直接调用B里面的方法,且不需要实例化B,不需要B做单例,也不需要静态被调用的方法,还能混入多个类,这对方法复用带来的极大的便利性,破除了众多限制。
要使用 mixin,请使用 with 关键字,后跟一个或多个 mixin 名称。 以下示例显示了两个使用 mixins 的类:
class Musician extends Performer with Musical {
// ···
}
class Maestro extends Person with Musical, Aggressive, Demented {
Maestro(String maestroName) {
name = maestroName;
canConduct = true;
}
}
要实现 mixin,请创建一个扩展 Object 且不声明构造函数的类。 除非你希望你的 mixin 可以用作常规类,否则请使用 mixin 关键字而不是 class。 例如:
mixin Musical {
bool canPlayPiano = false;
bool canCompose = false;
bool canConduct = false;
void entertainMe() {
if (canPlayPiano) {
print('Playing piano');
} else if (canConduct) {
print('Waving hands');
} else {
print('Humming to self');
}
}
}
有时你可能想限制可以使用混合的类型。 例如,mixin 可能依赖于能够调用 mixin 未定义的方法。 如以下示例所示,您可以通过使用 on 关键字指定所需的超类来限制 mixin 的使用:
class Musician {
// ...
}
mixin MusicalPerformer on Musician {
// ...
}
class SingerDancer extends Musician with MusicalPerformer {
// ...
}
在前面的代码中,只有扩展或实现 Musician 类的类才能使用 mixin MusicalPerformer。 因为 SingerDancer 扩展了 Musician,所以 SingerDancer 可以混入 MusicalPerformer。
Class variables and methods
使用 static 关键字来实现类范围的变量和方法。
静态变量
静态变量(类变量)对于类范围的状态和常量很有用:
class Queue {
static const initialCapacity = 16;
// ···
}
void main() {
assert(Queue.initialCapacity == 16);
}
静态变量在使用之前不会被初始化。
注意:常量名称首选 lowerCamelCase。
Static methods
静态方法(类方法)不对实例进行操作,因此无权访问 this。 但是,它们确实可以访问静态变量。 如以下示例所示,您直接在类上调用静态方法:
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);
}
注意:对于常见或广泛使用的实用程序和功能,请考虑使用顶级函数而不是静态方法。
可以使用静态方法作为编译时常量。 例如,可以将静态方法作为参数传递给常量构造函数。