前言
Dart是一种面向对象的语言,具有类和混合继承。 每个对象都是一个类的实例,所有类都来自Object。 混合继承意味着虽然每个类(除了Object)只有一个超类(也称为父类),但是类体可以在多个类层次结构中重用。
如何使用类成员
对象拥有由函数和数据(分别为方法和实例变量)组成的成员。在一个对象上调用方法时:该方法可以访问该对象的函数和数据。
用逗号(.)去获取一个实例的变量或者方法:
var p = Point(2, 2);
// 设置实例 p 的 属性 y 为 3
p.y = 3;
// 获取实例 p 的属性 y
assert(p.y == 3);
// 调用实例 p 的 distanceTo()
num distance = p.distanceTo(Point(4, 4));
当最左边的操作数为null时,请用?.来代替.以避免异常发生
// 当 p 为非空时, 才会把 y 设置为 4
p?.y = 4;
利用构造函数
你可以用构造函数来创建一个对象, 构造函数的名字可以是 ClassName 或者 ClassName.identifier。例如,下面的代码用Point()和Point.fromJson()构造函数创建Point对象:
var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});
下面的代码有相同的作用,但是在构造函数名字前用了一个可选的关键字new:
var p1 = new Point(2, 2);
var p2 = new Point.fromJson({'x': 1, 'y': 2});
注意版本:关键字
new在Dart2中变为可选的
一些类提供了常量构造函数。为了创建一个编译时的常量可以使用常量构造函数,把const关键字放在构造函数名称前就可以了:
var p = const ImmutablePoint(2, 2);
构建两个相同的编译时常量会产生一个单个,标准的实例:
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);
assert(identical(a, b)); // a和b是同一个实例
在一个常量上下文中,你可以在构造函数或者文字前省略const。例如,看下下面的代码,创建了一个map常量:
// 茫茫多的 const 关键字
const pointAndLine = const {
'point': const [const ImmutablePoint(0, 0)],
'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};
你可以省略除了第一个的其他const关键字:
// 只要一个 const 就可以建立常量上下文
const pointAndLine = {
'point': [ImmutablePoint(0, 0)],
'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};
如果一个常量构造函数在常量上下文的外面,并且没有const调用, 它会创建一个非常量的对象:
var a = const ImmutablePoint(1, 1); // 创建一个常量
var b = ImmutablePoint(1, 1); // 没有创建一个常量
assert(!identical(a, b)); // 不是同一个实例
注意版本:关键字
const在 Dart2 的常量上下文中才变可选
获取一个对象的类型
在运行时获取一个对象的类型,你可以使用Object的runtimeType属性,它会返回一个Type对象。
print('The type of a is ${a.runtimeType}');
看到这里,你已经知道怎么去使用classes了。剩下的章节教你如何去实现classes
实例变量
class Point {
num x; //申明一个实例变量x, 初始值null
num y; //申明一个y,初始值null
num z = 0; //申明一个z,初始值0
}
所有没有初始化过的实例变量,默认值都是 null
所有实例变量会生成一个隐式的的getter方法。所有的非 final 定义的实例变量会生成一个隐式的setter方法。
class Point {
num x;
num y;
}
void main() {
var point = Point();
point.x = 4; // 使用了 x 的 `setter` 方法.
assert(point.x == 4); // 使用了 x 的 `getter` 方法.
assert(point.y == null); // y 没有赋值的情况下默认 `getter` 方法拿到的值是 `null`.
}
如果你在申明时初始化了一个实例变量(代替了在构造函数或者方法中),当实例被创建时就会被赋值,相当于在构造函数和它的初始化列表执行之前。
构造函数
创建一个跟类同名的函数来申明一个构造函数(增加,可选,一个额外的identifier称为命名构造函数)。下面是构造函数最普通的模式,制造型的构造函数,用来创建一个类的实例:
class Point {
num x, y;
Point(num x, num y) {
// 有更好的方式实现这种赋值
this.x = x;
this.y = y;
}
}
this关键字指的是当前的实例。
注意:只有当存在一个命名冲突时使用
this,另外Dart的风格是省略this
通过构造函数给实例变量赋值的方式非常普遍,所以 Dart 有语法糖让它变得更简单:
class Point {
num x, y;
// 在构造函数方法体中实现变量的赋值
Point(this.x, this.y);
}
默认构造函数
即使你不申明一个构造函数,Dart 会默认提供一个给你。默认构造函数没有参数并且会触发超类(父类)的无参构造函数。
构造函数不能被继承
子类不能继承它们超类的构造函数。一个子类申明无参构造函数只能是默认的(无参数,无命名)构造函数。
命名构造函数
用一个命名构造函数实现多个构造函数,来为一个类提供更清晰的表达:
class Point {
num x, y;
Point(this.x, this.y);
// 命名构造函数
Point.origin() {
x = 0;
y = 0;
}
}
请记住,构造函数不是继承的,这意味着超类的命名构造函数不会被子类继承。 如果希望使用超类中定义的命名构造函数创建子类,则必须在子类中实现该构造函数。
调用非默认的超类构造函数
默认情况下,子类中的构造函数调用超类的未命名的无参数构造函数。 超类的构造函数会在构造函数体的开头被调用。 如果还使用初始化列表,则在调用超类之前执行。 总之,执行顺序如下:
- 初始化列表
- 超类的无参数构造函数
- 主类的无参数构造函数
如果超类没有未命名的无参数构造函数,则必须手动调用超类中的一个构造函数。 在冒号(:)之后指定超类构造函数,就在构造函数体(如果有)之前。
在下面的示例中,Employee类的构造函数为其超类Person调用命名构造函数。
class Person {
String firstName;
Person.fromJson(Map data) {
print('in Person');
}
}
class Employee extends Person {
// Person 没有一个默认的构造函数
// 你必须调用 super.fromJson(data).
Employee.fromJson(Map data) : super.fromJson(data) {
print('in Employee');
}
}
main() {
var emp = new Employee.fromJson({});
// 输出:
// in Person
// in Employee
if (emp is Person) {
// Type check
emp.firstName = 'Bob';
}
(emp as Person).firstName = 'Bob';
}
因为在调用构造函数之前会计算超类构造函数的参数,所以参数可以是一个表达式,例如函数调用:
class Employee extends Person {
Employee() : super.fromJson(getDefaultData());
// ···
}
警告:超类构造函数的参数无权访问它。 例如,参数可以调用静态方法,但不能调用实例方法。
初始化列表
除了调用超类构造函数之外,还可以在构造函数体运行之前初始化实例变量。 用逗号分隔初始化程序。
// 初始化列表会在构造函数体运行之前设置实例变量
Point.fromJson(Map<String, num> json)
: x = json['x'],
y = json['y'] {
print('In Point.fromJson(): ($x, $y)');
}
警告:初始化程序的右侧无权访问
this。
在开发期间,你可以使用初始化列表中的assert来验证输入。
Point.withAssert(this.x, this.y) : assert(x >= 0) {
print('In Point.withAssert(): ($x, $y)');
}
初始化列表时可以很方便设置final字段,以下示例初始化初始化列表中的三个final字段。
import 'dart:math';
class Point {
final num x;
final num y;
final num distanceFromOrigin;
Point(x, y)
: x = x,
y = y,
distanceFromOrigin = sqrt(x * x + y * y);
}
main() {
var p = new Point(2, 3);
print(p.distanceFromOrigin);
}
重定向构造函数
有时构造函数的唯一目的是重定向到同一个类中的另一个构造函数。 重定向构造函数的主体是空的,构造函数调用出现在冒号(:)之后。
class Point {
num x, y;
// 这个类的主构造函数
Point(this.x, this.y);
// 委托给了主构造函数
Point.alongXAxis(num x) : this(x, 0);
}
常量构造函数
如果你的类生成对象后永远不会更改,那么可以使这些对象成为编译时常量。 为此,请定义const构造函数并确保所有实例变量都是final。
class ImmutablePoint {
static final ImmutablePoint origin =
const ImmutablePoint(0, 0);
final num x, y;
const ImmutablePoint(this.x, this.y);
}
工厂构造函数
在实现并不总是创建一个类新实例的构造函数时,请使用factory关键字。 例如,工厂构造函数可能从缓存中返回实例,或者它可能返回子类型的实例。
以下示例演示了从缓存中返回对象的工厂构造函数:
class Logger {
final String name;
bool mute = false;
// _cache 是库私有的
static final Map<String, Logger> _cache =
<String, Logger>{};
factory Logger(String name) {
return _cache.putIfAbsent(
name, () => Logger._internal(name));
}
Logger._internal(this.name);
void log(String msg) {
if (!mute) print(msg);
}
}
注意:工厂构造函数没有权限访问
this
像调用任何其他构造函数一样调用工厂构造函数:
var logger = Logger('UI');
logger.log('Button clicked');