Dart是一种面向对象的语言,支持基于mixin的继承。每个对象都是一个类的实例,所有类都继承自Object。 基于Mixin的继承意味着虽然每个类(除了Object)只有一个超类,一个类可以继承自多个父类(多继承)。
类的定义
由关键字class类声明一个类,类的命名首字母一般大写,变量名使用驼峰命名规则:
1
2
class Point {
}
构造函数
通过创建与类同名的函数来声明构造函数(另外,命名构造函数中也可以加入一些可选附加标识符)。
1
2
3
4
5
6
7
8
9
class Point {
// 声明实例变量x,y
num x ,y;
// 构造函数
Point(num x, num y) {
this.x = x;
this.y = y;
}
}
使用Dart语法糖会更加简单,this关键字指向了当前类的实例:
1
2
// 使用Dart语法糖
Point(this.x, this.y);
默认构造函数
如果未声明构造函数,则提供默认的构造函数,默认构造函数没有参数,并在超类中调用无参数构造函数。这一点类似于C++。
构造函数不能继承
子类不从其父类继承构造函数。没有声明构造函数的子类,系统提供默认(无参数,无名称)构造函数。
命名的构造函数
使用命名构造函数可以从其他类或现有数据中快速实现构造函数:
1
2
3
4
5
6
7
8
9
10
11
class Point {
num x, y;
Point(this.x, this.y);
// 命名构造函数Named constructor
Point.origin() {
x = 0;
y = 0;
}
}
- 构造函数不能继承,这意味着超类的命名构造函数不会被子类继承。如果希望使用超类中定义的命名构造函数去创建子类,则必须在子类中实现该构造函数。
使用构造函数
构造函数可以是ClassName或者ClassName.identifier。例如:
1
2
3
var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});
var p3 = new Point(3, 3);
其中的new关键字是可选的。
有些类提供常量构造函数,将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)); // They are the same instance!
使用类成员
对象具有由函数和成员数据(分别为方法和实例变量)组成。可以使用点(.)来引用实例变量或方法:
1
2
var p = Point(2, 2);
p.y = 3;
用?表示可能为null
1
p?.y = 4;
获取对象的类型
要在运行时获取对象的类型,可以使用Object的runtimeType属性,该属性返回Type对象:
1
print('a的类型:${a.runtimeType}');
实例化变量
所有未初始化的实例变量都具有该值null:
1
2
3
4
class Point {
num x ; //声明实例变量x,初始值为null。
num y = 0 ; //声明y,初始值为0.
}
所有实例变量都隐式生成一个setter方法。非空实例变量也会隐式生成getter方法。
1
2
3
4
5
6
7
8
9
10
11
class Point {
num x;
num y;
}
main() {
var point = new Point();
point.x = 4; // Use the setter method for x.
assert(point.x == 4); // Use the getter method for x.
assert(point.y == null); // Values default to null.
}
调用父类的非默认构造函数
默认情况下,子类只能调用父类中未命名的无参数构造函数。父类的构造函数在子类构造函数前被调用。如果还使用initializer list,则先调用initializer list中的内容,然后执行弗雷中的未命名的无参数构造函数,最后调用子类未命名的无参数构造函数。执行顺序如下:
- initializer list(初始化列表)
- super class’s no-arg constructor(父类的无参数构造函数)
- main class’s no-arg constructor(主类的无参数构造函数)
如果父类没有未命名的无参数构造函数,则必须手动调用父类中的一个构造函数。父类构造函数在子类构造函数名后的冒号’:‘之后调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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');
}
}
其参数还可以是一个表达式:
1
2
3
4
class Employee extends Person {
Employee() : super.fromJson(getDefaultData());
// ···
}
父类构造函数的参数无权访问this。例如,参数可以调用静态方法,但不能调用实例方法。
初始化列表
除了调用父类构造函数,还可以在构造函数体运行之前初始化实例变量。变量之间用逗号’,‘分隔。
1
2
3
4
5
Point.fromJson(Map<String, num> json)
: x = json['x'],
y = json['y'] {
print('In Point.fromJson():($x, $y)');
}
- 初始化程序的右侧不能访问this
重定向构造函数
有时构造函数的目的只是重定向到该类中的另一个构造函数。重定向构造函数的主体是空的,即没有函数体,构造函数的调用放在冒号‘:’之后。
1
2
3
4
5
6
7
class RedirectPoint {
num x, y;
// 主题构造函数
RedirectPoint(this.x, this.y);
// 重定向构造函数,指向主构造函数,函数体为空
RedirectPoint.alongXAxis(num x) : this(x, 0);
}
常量构造函数
如果使用该类生成的对象永远不会改变,那么就可以让这些对象成为编译时常量。为此,使用const来定义构造函数,用final来定义所有的实例变量。
1
2
3
4
5
class ImmutablePoint {
final num x, y;
const ImmutablePoint(this.x, this.y);
static final ImmutablePoint origin = const ImmutablePoint(0, 0);
}
工厂构造方法
当实现一个使用factory关键字修饰的构造函数时,这个构造函数不必创建该类的新实例。例如,一个工厂构造方法可能从缓存中返回一个实例,或者它可能返回一个子类的实例。
下面例子从缓存中返回一个实例:
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
26
27
28
class Logger {
final String name;
bool mute = false;
// _cache ‘_’代表私有属性
static final Map<String, Logger> _cache = <String, Logger>{};
factory Logger(String name) {
if (_cache.containsKey(name)) {
return _cache[name];
} else {
final logger = Logger._internal(name);
_cache[name] = logger;
return logger;
}
}
Logger._internal(this.name);
void log(String msg) {
if (!mute) {
print(msg);
}
}
}
// 调用工厂构造函数
var logger = Logger('UI');
logger.log('log');
- 工厂构造方法也不能访问this
方法
方法就是为对象提供行为的函数。
实例方法
对象的实例方法可以访问对象的实例变量和this。
1
2
3
4
5
6
7
8
9
10
11
12
13
import 'dart:math';
// 实例方法
class Point {
num x, y;
Point(this.x, this.y);
num distanceTo(Point other) {
var dx = x - other.x;
var dy = y - other.y;
return sqrt(dx * dx + dy * dy);
}
}
Getters 和 Setters
getter和setter是提供对象属性读写访问权限的特殊方法。每个实例变量都有一个隐式getter,如果合适的话还有一个setter。可以使用get和set关键字来实现getter和setter方法创建其他属性 :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Rectangle {
num left, top, width, height;
Rectangle(this.left, this.top, this.width, this.height);
// 定义两个计算属性 righ ,bottom
num get right => left + width;
set right(num value) => left = value - width;
num get bottom => top + height;
set bottom(num value) => top = value - height;
}
// 调用
var rect = dartclasses.Rectangle(2, 4, 20, 15);
rect.right = 12;
print('rect.left:${rect.left}, rect.top:${rect.top}');
抽象方法
实例,getter和setter方法可以是抽象的,定义一个接口,但将其实现留给其他类。抽象方法只能存在于抽象类中。
要使方法成为抽象方法,需要使用分号‘;’,而不是方法体:
1
2
3
4
5
6
7
8
9
10
11
// 抽象方法
abstract class Doer {
// 定义实例变量和方法,这里定义了一个抽象方法
void doSomething();
}
class EffectiveDoer extends Doer {
void doSomething() {
// 提供方法的实现,所以这里的方法不是抽象的
}
}
抽象类
使用abstract修饰符定义的无法实例化的类称为抽象类。抽象类在定义接口时非常有用,通常还包含了一些实现。如果让抽象类看起来是可实例化的,可以定义工厂构造函数。
抽象类通常包含抽象方法。例如:
1
2
3
4
5
6
7
// 抽象类, 不能被实例化
abstract class AbstractContainer {
// 定义构造函数,域,方法...
// 抽象方法
void updateChildren();
}
隐式接口
每个类都隐式定义了一个接口,该接口包含该类的所有实例成员及其实现的所有接口。如果要在不继承B的情况下创建支持B类API的A类,则A类应实现B的接口。
一个类通过implements子句声明,来实现一个或多个接口,然后提供接口所需的API。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 隐式接口
class ImplicitPerson {
// 定义私有变量
final _name;
// 构造函数
ImplicitPerson(this._name);
// 接口
String greet(String who) => 'Hello, $who, I am $_name';
}
// Person的接口实现
class Impostor implements ImplicitPerson {
get _name => '';
String greet(String who) => 'Hi $who, Do you know who I am?';
}
// 调用
String greetBob(dartclasses.ImplicitPerson perspn) => perspn.greet('Bob');
print(greetBob(dartclasses.ImplicitPerson('Kathy')));
print(greetBob(dartclasses.Impostor()));
这是一个指定类实现多个接口的示例:
1
class Point implements Comparable, Location {...}
类的扩展
使用extends创建一个子类,同事supper将指向父类:
1
2
3
4
5
6
7
8
9
10
class Television {
void turnOn() {
}
}
class SmartTelevision extends Television {
void turnOn() {
super.turnOn();
}
}
重写成员
使用@override来表明重写一个成员
1
2
3
4
5
6
class SmartTelevision extends Television {
@override
void turnOn() {
// ...
}
}
- 要在类型安全的代码中缩小方法参数或实例变量的类型,可以使用covariant关键字。
可重写的运算符
< |
+ |
| |
[] |
> |
/ |
^ |
[]= |
<= |
~/ |
& |
~ |
>= |
* |
<< |
== |
– |
% |
>> |
- !=不是一个可重写的运算符。表达式e1 != e2只是!(e1 == e2)的语法糖。
重写+和-运算符的示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 重写运算符
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);
// Operator == and hashCode not shown.
}
void main() {
final v = Vector(2, 3);
final w = Vector(2, 2);
assert(v + w == Vector(4, 5));
assert(v - w == Vector(0, 1));
}
- 如果重写==,则还应重写Object的hashCode的getter。
noSuchMethod()
如果使用不存在的方法或实例变量时,可以重写noSuchMethod():
1
2
3
4
5
6
7
8
class A {
// 除非重写 noSuchMethod, 否者将会抛出 NoSuchMethodError.
@override
void noSuchMethod(Invocation invocation) {
print('You tried to use a non-existent member: ' +
'${invocation.memberName}');
}
}
如果不满足以下情况,那么无法调用未实现的方法:
- 接收器具有静态类型dynamic;
- 接收器有一个静态类型,它定义了未实现的方法(abstract is OK),接收器的动态类型和Object类对象的noSuchMethod()实现不同。
枚举类型
枚举类型(通常称为enumerations或enums)是一种特殊类,用于表示固定数量的常量值。使用关键字enum来声明。
1
enum Color { red, green, blue }
使用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 枚举
List<dartclasses.Color> colors = dartclasses.Color.values;
print(colors);
var aColor = dartclasses.Color.blue;
switch (aColor) {
case dartclasses.Color.red:
print('Red');
break;
case dartclasses.Color.blue:
print('Blue');
break;
case dartclasses.Color.green:
print('Green');
break;
}
枚举类型具有以下限制:
- 不能子类化,mix in或实现枚举。
- 无法显式实例化枚举
为类添加特征:mixins
Mixins是一种在多个类层次结构中重用类代码的方法。
要使用 mixin,需要在with关键字后跟一个或多个mixin的名称。
要实现 mixin,要创建一个继承Object的子类,并且不声明构造函数,然后使用mixin关键字来替换掉class。
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// mixin
mixin Musical {
bool canPlayPiano = false;
bool canCompose = false;
bool canConduct = false;
void entertainMe() {
if (canPlayPiano) {
print('Playing piano');
} else if (canCompose) {
print('Waving hands');
} else {
print('Humming to self');
}
}
}
// Performer类
class Performer {
// ...
void doSomething(){
}
}
// 使用mixin: Musical
class Musician extends Performer with Musical {
}
// mixin
mixin Aggressive {
}
// mixin
mixin Demented {
}
// 使用mixin: Musical, Aggressive, Demented
class Maestro extends Performer
with Musical, Aggressive, Demented {
var name;
bool canConduct;
Maestro(String maestroName) {
name = maestroName;
canConduct = true;
}
}
Dart 2.1中引入了mixin对关键字的支持,要指定只有某些类型可以使用mixin,你的mixin可以调用它没有定义的方法–使用关键字on来指定所需的超类:
1
2
3
mixin MusicalPerformer on Musician {
// ···
}
类变量和方法
使用static关键字实现类变量和方法。
- 静态变量
静态变量对类状态和常量是很有用的,它在使用之前不会被初始化。
1
2
3
4
5
6
7
8
class Queue {
static const initialCapacity = 16;
// ···
}
void main() {
assert(Queue.initialCapacity == 16);
}
- 静态方法
静态方法不能对实例进行操作,并且不能访问this。
1
2
3
4
5
6
7
8
9
10
11
12
import 'dart:math';
class Point {
num x, y;
Point(this.x, this.y);
static num distanceBetween(Point a, Point b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
}
可调用的类
要允许像函数一样调用Dart类的实例,需要实现call()方法。
在下面的示例中,WannabeFunction类定义了一个call()函数,
1
2
3
4
5
6
7
8
9
class WannabeFunction {
call(String a, String b, String c) => '$a $b $c!';
}
main() {
var wf = new WannabeFunction();
var out = wf("Hi","there,","gang");
print('$out');
}