Dart是面向对象的语言,所有对象都是一个类的实例,所有类都继承自Object
。此外,Dart
支持基于mixin
的继承机制,这意味着,每个类(除了Object
)都只有一个父类,一个类的代码可以在其他多个类继承中重复使用。
一、类的实例化
类的实例化,可以使用new
关键字,后面可以接上构造函数
(如ClassName
或者ClassName.identifier
),如:
var jsonData = JSON.decode('{"x":1, "y":2}');
// 直接接 ClassName
var p1 = new Point(2, 2);
// 接 ClassName.identifier
var p2 = new Point.fromJson(jsonData);
如果要访问对象的成员(属性
或者方法
),可以使用.
语法:
var distance = p1.distanceTo(p2);
还可以使用?.
来避免左边对象为null
时抛出异常:
p?.y = 4; // 如果p为非null,才会执行 p.y = 4;
有些类提供了常量构造函数
,可以创造编译时常量
,那么这种构造函数的实例化不采用new
关键字,而是采用const
关键字,如下:
var p = const ImmutablePoint(2, 2);
两个一样的编译时常量是同一个对象,用identical
测试返回true
:
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);
identical(a, b); // true
此外,runtimeType
可以获得一个示例的类型,该属性返回一个Type
对象:
main() {
int a = 1;
print('type of a is ${a.runtimeType}');
}
// 输出:type of a is int
二、类的定义
使用class
关键字可以声明一个类:
class Foo {
}
1)实例变量
属于类的实例的变量称为实例变量
,实例变量
使用var propertyName;
或者type propertyName
声明在class
体里,如果一个实例变量
没有赋予初始值,则它的初始值是null
,如:
class Point {
num x; // 初始值 null
num y; // 初始值 null
num z;
}
每个实例变量都会隐含地生成一个getter
方法,对于非final
的变量还会隐含地生成一个setter
,所以可以:
var point = new Point();
point.x = 4; // 调用了 setter 方法来设置变量值
point.y; // 调用了 getter 方法来获取变量值
注意: 如果在定义实例变量时初始化了
实例变量
,那么实例变量的值是在实例创建时、构造函数和初始化参数列表 执行前 初始化的
2)构造函数
class
中命名和类名
一致的那个函数是构造函数
,构造函数会在类
被实例化时调用,如:
class Point {
num x;
num y;
Point(num x, num y) {
this.x = x;
this.y = y;
}
}
其中this
关键字指向当前的实例。不过,只有当名字冲突时才使用this
,Dart
中在类里是可以忽略this
的,也就是说,我们可以这么写:
class Point {
num x;
num y;
Point(num _x, num _y) {
x = _x;
y = _y;
}
}
或者,也可以直接在构造函数中使用初始化赋值
的语法糖:
class Point {
num x;
num y;
Point(this.x, this.y);
}
1、默认构造函数
如果在一个类中没有定义构造函数,则会有个默认的构造函数。默认的构造函数无参数,且会调用没有参数的构造函数。
2、构造函数不会继承
子类
不会继承父类
的构造函数(除非是无名无参的构造函数)
3、命名构造函数
使用命名构造函数可以实现为一个类指定多个构造函数
,也可以通过这来更清晰地表达意图,如:
class Point {
num x;
num y;
Point(this.x, this.y);
Point.fromJson(Map json) {
x = json['x'];
y = json['y'];
}
}
4、调用父类的构造函数
默认情况下,子类的构造函数会自动调用父类的无名无参的默认构造函数
,父类的构造函数在子类构造函数的函数体开头位置调用。但是如果提供了初始化参数列表
,则初始化参数列表会在父类构造函数
之前执行,也就是说,执行顺序如下:
- 初始化参数列表
- 父类无名构造函数
- 子类无名构造函数
如果超类中没有无名无参
构造函数,那么就需要手动调用了,调用方法为在构造函数
之后使用:
调用,如:
class Point {
num x;
num y;
Point(this.x, this.y);
}
class Point3D extends Point {
num z;
Point3D(num x, num y, num z): super(x, y) {
this.z = z;
}
}
由于父类构造函数
是在子类构造函数
执行前执行的,所以参数可以是一个表达式或者一个方法调用,如:
class A {
A(String str) {
print(str);
}
}
class B extends A {
B(): super(getDefaultData());
static getDefaultData() {
return 'Hello, world';
}
}
注意:如果是一个类中的
方法
调用,那么只能使用静态方法
5、初始化列表
初始化列表除了可以调用父类构造函数,还可以初始化实例参数,采用,
分隔表达式,如:
class Point {
num x;
num y;
Point(this.x, this.y);
Point.fromJson(Map jsonMap):
x = jsonMap['x'],
y = jsonMap['y'] {
print('($x, $y)');
}
}
需要注意的是:初始化表达式=
右边的部分不能访问this
,此外,对于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);
}
6、重定向构造函数
有时候一个构造函数
会调用类中的其他构造函数,这种构造函数称之为重定向构造函数
,如下:
class Point {
num x;
num y;
Point(this.x, this.y);
Point.alongXAxis(num x): this(x, 0);
}
7、常量构造函数
如果类提供的是状态不变的对象,那么可以把这些对象定义为编译时常量
,实现这种功能可以定义const
构造函数,且声明所有类的变量为final
,如:
class ImmutablePoint {
final num x;
final num y;
const ImmutablePoint(this.x, this.y);
static final ImmutablePoint origin = const Immutable(0, 0);
}
8、工厂方法构造函数
如果一个构造函数并不总是返回一个新的对象,那么可以在构造函数前面加上factory
关键字,来表示它是一个工厂方法构造函数
。但是需要注意的是,工厂构造函数中不能访问this
,如下:
class Logger {
final String name;
bool mute = false;
// 缓存实例
static final Map<String, Logger> _cache = <String, Logger>{};
factory Logger(String name) {
if (_cache.containsKey(name)) {
return _cache[name];
} else {
final logger = new Logger._internal(name);
_cache[name] = logger;
return logger;
}
}
Logger._internal(this.name);
void log(String msg) {
if (!mute) {
print(msg);
}
}
}
那么,在使用new
关键词实例化Logger
时,每次都会调用factory Logger(String name)
这个构造函数:
var logger1 = new Logger('UI');
var logger2 = new Logger('UI');
identical(logger1, logger2); // true
3)实例方法
实例方法可以访问this
,Dart
中实例方法的声明如下:
class A {
methodName() {
// ...
}
}
4)setter/getter
在一个方法前面加上setter
或getter
关键字,那么这个方法就成为了setter
和getter
,就可以对instance.methodName
进行赋值或者取值操作,而无需使用()
来调用,如:
class Person {
String firstName;
String lastName;
Person(this.firstName, this.lastName);
String get fullName {
return '$firstName $lastName';
}
}
final me = new Person('Ruphi', 'Lau');
print(me.fullName); // 输出:Ruphi Lau
注意,getter
里不能带()
,即get name()
是错误的,get name
才是正确的,而setter
里则可以接受一个参数,即为赋值传入的值:set name(String name)
三、操作符重写
Dart
中支持操作符重写,可被重写的操作符有:
- 比较运算符:
>
,<
,<=
,>=
,==
- 算数运算符:
+
,-
,*
,/
,%
,~/
- 位运算符:
|
,&
,^
,~
,<<
,>>
- 方括号运算符:
[]
,[]=
重写运算符的语法为使用operator
关键字紧接运算符,以下例子为实现向量
的运算:
class Vector {
final int x;
final int y;
const Vector(this.x, this.y);
Vector operator +(Vector v) {
return new Vector(x + v.x, y + v.y);
}
Vector operator -(Vector v) {
return new Vector(x - v.x, y - v.y);
}
}
main() {
final v = new Vector(2, 3);
final w = new Vector(2, 2);
final addRes = v + w;
print('(${addRes.x}, ${addRes.y})'); // 输出:(4, 5)
final minusRes = v - w;
print('(${minusRes.x}, ${minusRes.y})'); // 输出:(0, 1)
}
四、抽象类
1)抽象类的定义
不能被实例化的类是抽象类,抽象类通常用来定义接口及部分实现。如果抽象类要被实例化,则需要定义一个工厂构造函数
。抽象类的声明使用abstract
修饰符:
abstract class AbstractContainer {
// ...
}
2)抽象函数
抽象函数是之定义函数接口但是没有实现(方法体)的函数,抽象函数由子类实现,调用一个未实现的抽象函数会导致运行时异常。如下:
abstract class Doer {
void doSomething(); // 抽象函数,没有方法体
}
class EffectiveDoer extends Doer {
void doSomething() {
// 在子类中实现
}
}
3)隐式接口
每个类都隐式地定义了一个包含所有实例成员的接口,并且这个类实现了该接口。Dart
中并没有直接提供interface
这样子的关键字,因此定义interface
应该通过定义一个类实现,如果只想支持某个类的接口但是不想继承它的实现,那么使用implements
关键字即可,如下:
class Person {
final name;
Person(this.name); // 构造函数不会创建接口
String greet(who) => 'Hello, $who. I am $name'; // 包含了 greet 的实现
}
class Stark implements Person {
final name = 'Tony Stark';
String greet(who) => '$who, I am, I am $name';
}
main() {
final ironMan = new Stark();
print(
ironMan.greet('Thanos')
);
}
接口是可以多实现
的,如下:
class TonyStart implements American, Scientist, Richman, Playboy {
// ...
}
五、类的继承
类的继承,采用extends
关键字,而子类
中可以使用supper
来引用父类
,如下:
class TV {
void turnOn() {
_illuminateDisplay();
_activateIrSensor();
}
// ...
}
class SmartTV extends TV {
void turnOn() {
super.turnOn();
_bootNetworkInterface();
_initializeMemory();
_upgradeApps();
}
// ...
}
子类可以覆写实例函数
,getter
和setter
。以下例子为覆写noSuchMethod()
函数(这个函数为Object
类中当调用了对象上不存在的函数所触发的):
class A {
void noSuchMethod(Invocation mirror) {
print('You tried to use a non-existent member: ${mirror.memberName}');
}
}
此外,可以使用@override
注解来表明是想要覆写超类的一个函数,如:
class A {
@override
void noSuchMethod(Invocation mirror) {
// ...
}
}
六、枚举类型
枚举类型是一种特殊的类
,用来表示枚举,使用enum
关键字可以定义枚举:
enum Color {
RED,
GREEN,
BLUE
}
枚举类型中的每个值都有一个index getter
,返回枚举值在定义中出现的位置(从0开始):
Color.RED; // 0
Color.GREEN; // 1
Color.BLUE; // 2
可以使用values
来获得所有的枚举值,如:
List<Color> colors = Color.values;
若是在switch
语句中使用枚举,那么需要处理枚举类型的所有值,或者定义一个default
分支,否则会导致抛出一个警告:
Color someColor = Color.RED;
switch (someColor) {
case Color.RED:
// ...
break;
case Color.GREEN:
// ...
break;
// 会报错,因为没有对 Color.BLUE 进行处理
}
Dart中的枚举类型,有如下的限制:
- 无法继承枚举类型,无法使用mixin,无法实例化枚举
- 无法显示地初始化一个枚举类型
七、Mixins
Mixins
是一种在多类继承中重用一个类代码的手段,可以为类添加新的功能。使用Mixins
的方法为使用with
关键字,如下:
class Person {
final name;
Person(this.name);
}
class Program {
program() => print('Program');
}
class Reading {
reading() => print('Reading');
}
class Tom extends Person with Program, Reading {
Tom(): super('Tom') {
print('$name can:');
program();
reading();
}
}
main() {
new Tom();
}
以上代码输出:
Tom can:
Program
Reading
如果一个类继承Object
,但是该类没有构造函数,那么就不能调用super
,这个类就是一个mixin
,如:
abstract class 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');
}
}
}
从Dart 1.13
开始,Dart中的Mixins
以下限制不再那么严格了:
Mixins
可以调用其他类,不再限制为继承Object
Mixin
可以调用super()
八、静态变量与静态函数
可以使用static
关键字来定义静态变量
和静态函数
,他们属于类自身,不属于任意一个实例。如下:
class Chinese {
static const from = 'China';
static whereAreYouFrom() {
print('I am from $from');
}
}
需要注意的是:
静态方法
由于属于类
,但是this
代指的是实例对象,所以静态方法不能访问this
静态方法
可以访问其他静态方法
和静态变量