dart是一种面向对象的语言,具有类和基于mixin的继承。每个对象都是一个类的实例,所有的类都是
Object
的子类。基于mixin的继承意味着,尽管每个类(Object除外)只有一个超类,但一个类主体可以在多个类层次中重用。
使用类成员
对象的成员包括函数和数据(分别是方法和实例变量)。当你调用一个方法时,你在一个对象上调用它:这个方法可以访问那个对象的函数和数据。
使用点(.
)引用实例变量或方法:
var p = Point(2, 2);
// 设置实例变量要的值
p.y = 3;
// 获取y的值
assert(p.y == 3);
// 在p上调用 distanceTo()
num distance = p.distanceTo(Point(4, 4));
使用?.
而不是.
为了避免最左边的操作对象为空时出现异常:
// 如果p不为空,设置它的变量y的值为4
p?.y = 4;
使用构造函数
你可以使用一个创建函数来创建对象。构造函数的名字可以是ClassName
或者ClassName.indentifier
。例如:以下的代码创建Point
对象使用Point()
和Point.formJson()
构造函数:
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});
版本说明:
key
关键词在版本2中为可选使用
一些类提供常量构造函数constant constructors
。使用一个常量构造函数去创建一个编译时常量,将 const
关键词放在构造函数名称之前:
var p = const ImmutablePoint(2, 2);
构造两个相同的编译时常量会产生一个规范的实例:
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);
assert(identical(a, b)); //他们是同一个对象
在常量上下文中,可以省略构造函数或常量前面的const
。例如,看看这个代码,它创建了一个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
关键字在Dart 2中的恒定上下文中变得可选。
获取对象的类型
在运行时获取对象的类型,你可以使用对象的runtimeType
属性,该属性返回一个Type
对象
print('The type of a is ${a.runtimeType}');
到目前为止,你已经知道如何使用类。接下去剩余的章节展示如何去实现类。
对象变量
这里展示你如何声明对象的变量:
class Point {
num x; // 声明变量x,初始化为null
num y; // 声明变量y,初始化为null
num z = 0; // 声明变量z,初始化为0
}
所有未初始化的对象变量拥有值null
所有实例变量都生成隐式getter
方法。Non-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); // 值默认为空。
}
如果初始化声明实例变量量(该变量不是在构造函数或方法中),则在创建实例时设置该值,该值在构造函数及其初始值列表之前执行。
构造函数
通过创建一个与其类同名的函数来声明一个构造函数(另外,还可以添加一个附加标识符,如命名构造函数中所述)。最常见的构造函数形式——生成构造函数——创建了一个类的新实例:
class Point {
num x, y;
Point(num x, num y) {
//有更好的方法来做到这一点,请继续往下看
this.x = x;
this.y = y;
}
}
this
关键词引用到当前的对象
注:只有在名称冲突时才使用此选项。否则,Dart样式会忽略这个。
将构造函数参数分配给实例变量的模式非常常见,Dart有语法糖以方便创建对象构造器:
class Point {
num x, y;
// 在构造函数运行前,语法糖会帮助设置x和y
Point(this.x, this.y);
}
默认的构造函数
如果不声明构造函数,则会为你提供默认构造函数。默认构造函数没有参数,并调用超类中的无参数构造函数。
构造函不能被继承
子类不从它们的超类继承构造函数。声明没有构造函数的子类只有默认(没有参数,没有名称)构造函数。
命名构造函数
使用命名构造函数为一个类实现多个构造函数,或者提供额外的声明:
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 {
Employee.fromJson(Map data) : super.fromJson(data) {
print('in Employee');
}
}
main() {
var emp = new Employee.fromJson({});
if (emp is Person) {
// 类型检查
emp.firstName = '永动机';
}
(emp as Person).firstName = '永动机';
}
-------------------------------------------------------------------
in Person
in Employee
因为超类构造函数的参数是在调用构造函数之前评估的,所以参数可以是表达式,如函数调用:
class Employee extends Person {
Employee() : super.fromJson(getDefaultData());
// ···
}
警告:超类构造函数的参数没有访问
this
的权限。例如,参数可以调用静态方法,但不能调用实例方法。
初始值列表
除了调用超类构造函数,还可以在构造函数主体运行之前初始化实例变量。用逗号分隔初始值设定项。
// 初始值列表在构造函数运行前初始化对象变量
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);
}
----------------------------------------------------------------------
3.605551275463989
重定向构造函数
有时构造函数的唯一目的是重定向到同一类中的另一个构造函数。重定向构造函数的主体为空,构造函数调用出现在冒号( :
)之后。
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) {
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);
}
}
注意:工厂构造者对
this
没有访问权限。
像调用任何其他构造函数一样调用工厂构造函数:
var logger = Logger('UI');
logger.log('Button clicked');
方法
方法是为对象提供行为的函数。
实例方法
在对象的实例方法有权限获取对象变量和this
,在接下来的例子里distanceTo
就是一个对象方法:
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
getters 和 setters 是提供对对象属性的读写访问的特殊方法。回想一下,每个实例变量都有一个隐式getter,如果合适的话,还有一个setter。你可以通过使用get
和set
关键字实现getters和setters来创建其他属性:
class Rectangle {
num left, top, width, height;
Rectangle(this.left, this.top, this.width, this.height);
// 定义两个计算属性:右侧和底部。
num get right => left + width;
set right(num value) => left = value - width;
num get bottom => top + height;
set bottom(num value) => top = value - height;
}
void main() {
var rect = Rectangle(3, 4, 20, 15);
assert(rect.left == 3);
rect.right = 12;
assert(rect.left == -8);
}
使用getters和setters,您可以从实例变量开始,然后用方法包装它们,所有这些都不需要更改客户端代码。
注意:无论是否明确定义了getter,increment ( ++ )等运算符都以预期的方式工作。为了避免任何意外的副作用,操作只调用getter一次,将其值保存在一个临时变量中。
抽象方法
对象、getter和setter方法可以是抽象的,定义一个接口,但将其实现留给其他类。抽象方法只存在于抽象类中。
要使方法抽象,请使用分号(;
)而不是方法体:
abstract class Doer {
//定义对象变量和方法...
void doSomething(); //定义抽象方法
}
class EffectiveDoer extends Doer {
void doSomething() {
// 提供一个实现,因此该方法在这里不是抽象的...
}
}
抽象的类
使用abstract
修饰符来定义一个抽象类(一个类不能被实例化)抽象类对于定义接口是非常有用的,经常有一些实现。如果你的抽象类想要能被实例化,请定义工厂构造函数
抽象类里的抽象方法
// 此对象爱你过是抽象类,因此不能被实例化
abstract class AbstractContainer {
// 定义构造函数、字段、方法...
void updateChildren(); // 抽象方法
}
隐式接口
每个类隐式定义一个接口,该接口包含该类及其实现的任何接口的所有实例成员。如果你想创建一个不继承B实现而支持B类API的A类,A类应该实现B接口。
一个类通过在implements
子句中声明一个或多个接口,然后提供接口所需的API来实现这些接口。例如:
// 隐式接口包含 greet()
class Person {
// 在接口中,但仅在此类中可见。
final _name;
//不在接口中,因为这是构造函数。
Person(this._name);
// 在接口中
String greet(String who) => 'Hello, $who. I am $_name.';
}
// 实现了Person接口
class Impostor implements Person {
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()));
}
下面是一个指定一个类实现多个接口的示例:
class Point implements Comparable, Location {...}
扩展类
使用extends
去创建子类,super
去引用超类:
class Television {
void turnOn() {
_illuminateDisplay();
_activateIrSensor();
}
// ···
}
class SmartTelevision extends Television {
void turnOn() {
super.turnOn();
_bootNetworkInterface();
_initializeMemory();
_upgradeApps();
}
// ···
}
重写成员
子类可以重写实例方法、getters和setters。您可以使用@override
注解来指示你想要重写的成员:
class SmartTelevision extends Television {
@override
void turnOn() {...}
// ···
}
要缩小代码中类型安全的方法参数或实例变量的类型,可以使用covariant
关键字。
重写运算符
你可以重写以下表中显示的运算符。例如,如果定义Vecor类,则可以定义+
方法来添加两个vectors。
< | + | | | [] |
---|---|---|---|
> | / | ^ | []= |
<= | ~/ | & | ~ |
>= | * | << | == |
– | % | >> |
注意:你可能已经注意到了
!=
不是可重写的运算符。表达式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);
//运算符= =和hashCode未显示。
}
void main() {
final v = Vector(2, 3);
final w = Vector(2, 2);
assert(v + w == Vector(4, 5));
assert(v - w == Vector(0, 1));
}
如果重写==
,则还应该重写对象的hashCode
getter。
noSuchMethod()
要在代码试图使用不存在的方法或实例变量时检测或做出反应,可以重写noSuchMethod ( ) :
class A {
// 除非重写noSuchMethod,否则使用不存在的成员会导致noSuchMethod错误。
@override
void noSuchMethod(Invocation invocation) {
print('You tried to use a non-existent member: ' +
'${invocation.memberName}');
}
}
除非下列情况之一成立,否则不能调用未实现的方法:
- 接收者具有静态类型
dynamic
。 - 接收者具有定义未实现方法的静态类型(抽象是可以的),接收者的动态类型具有不同于类
Object
中的noSuchMethod()
的实现。
枚举类型
枚举类型,通常称为枚举,是一种特殊类型的类,用于表示固定数量的常量值。
使用枚举
使用enum
关键词来声明一个枚举类型
enum Color { red, green, blue }
枚举中的每个值都有一个index
索引,它返回枚举声明中值的从零开始的位置。例如,第一个值具有索引0,第二个值具有索引1。
assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);
若要获取枚举中所有值的列表,请使用枚举的values
常量。
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: // 没有default,将会报错
print(aColor); // 'Color.blue'
}
枚举类型有以下限制:
- 你不能子类化、混合或实现枚举。
- 不能显式实例化枚举。
给类添加一个特性:混合(mixin)
混合(mixin)是一种在多个类层次中重用类代码的方法。
若要使用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,请创建一个扩展对象的类,声明没有构造函数,也没有对super
的调用。例如:
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');
}
}
}
注意:一些对mixin的限制正在被取消。有关详细信息,请参见建议的mixin规范。
类变量和类方法
使用static
关键字实现类范围的变量和方法。
静态变量
静态变量(类变量)对于类范围的状态和常量非常有用:
静态变量在使用之前不会初始化。
静态方法
静态方法(类方法)不能在实例操作,因此它没有访问this
的权限。例如:
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);
}
}
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);
}
注意:为了常用或广泛使用的实用程序和功能,考虑使用顶层函数,而不是静态方法。
可以使用静态方法作为编译时常量。例如,可以将静态方法作为参数传递给常量构造函数。
PS:本文
整理
自官方文档,若有发现问题请致邮 caoyanglee92@gmail.com