函数:是类中定义的方法,是类对象的行为。
一、Instance methods(实例函数)
对象的实例函数可以访问this
。 例如下面示例中的 distanceTo() 函数 就是实例函数:
import 'dart:math';
class Point {
num x;
num 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);
}
}
1.1.Getters and setters
Getters 和 setters 是用来设置和访问对象属性的特殊 函数。每个实例变量都隐含的具有一个 getter, 如果变量不是 final
的则还有一个 setter。 你可以通过实行 getter 和 setter 来创建新的属性, 使用 get
和 set
关键字定义 getter 和 setter:
class Rectangle {
num left;
num top;
num width;
num height;
Rectangle(this.left, this.top, this.width, this.height);
// 定义两个计算属性: right and 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;
}
main() {
var rect = new Rectangle(3, 4, 20, 15);
assert(rect.left == 3);
rect.right = 12;
assert(rect.left == -8);
}
- 借助于 getter 和 setter ,你可以直接使用实例变量,并且在不改变客户代码的情况下把他们包装成方法。
注意: 像 (++) 这种操作符不管是否定义 getter 都会正确的执行。 为了避免其他副作用, 操作符只调用 getter 一次,然后 把其值保存到一个临时变量中。
二、Abstract methods(抽象函数)
实例函数、 getter、和 setter 函数可以为抽象函数, 抽象函数是只定义函数接口但是没有实现的函数,由子类来 实现该函数。如果用分号来替代函数体,则这个函数就是抽象函数。
abstract class Doer {
// ...定义实例变量和函数...
void doSomething(); // 定义抽象函数.
}
class EffectiveDoer extends Doer {
void doSomething() {
// ...抽象函数在此实现后,这里的函数就不再是抽象的...
}
}
注意:调用一个没实现的抽象函数会导致运行时异常。
三、Overridable operators(可覆写的操作符)
下表中的操作符可以被覆写。 例如,如果你定义了一个 Vector 类, 你可以定义一个 +
函数来实现两个向量相加。
操作符 | 操作符 | 操作符 | 操作符 | 操作符 | 操作符 |
---|---|---|---|---|---|
< |
+ |
| |
[] |
> |
/ |
^ |
[]= |
<= |
~/ |
& |
~ |
>= |
* |
<< |
== |
- |
% |
>> |
下面是覆写了 + 和 - 操作符的示例:
class Vector {
final int x;
final int y;
const Vector(this.x, this.y);
/// Overrides + (a + b).
Vector operator +(Vector v) {
return new Vector(x + v.x, y + v.y);
}
/// Overrides - (a - b).
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);
// v == (2, 3)
assert(v.x == 2 && v.y == 3);
// v + w == (4, 5)
assert((v + w).x == 4 && (v + w).y == 5);
// v - w == (0, 1)
assert((v - w).x == 0 && (v - w).y == 1);
}
- 如果覆写了
==
,则还应该覆写对象的hashCode
getter 函数。
Dart 中的每个对象都有一个整数 hash 码,这样每个对象都 可以当做 map 的 key 来用。但是,你可以覆写 hashCode getter 来自定义 hash 码的实现,如果你这样做了,你也需要 同时覆写 ==
操作符。相等的对象(使用 ==
比较)的 hash 码应该一样。Hasm 码并不要求是唯一的, 但是应该具有良好的分布形态。
class Person {
final String firstName, lastName;
Person(this.firstName, this.lastName);
// 覆写 hashCode 的策略和 Effective Java第11章节一样.
int get hashCode {
int result = 17;
result = 37 * result + firstName.hashCode;
result = 37 * result + lastName.hashCode;
return result;
}
// 如果覆写 hashCode,通常应该实现操作符 ==.
bool operator ==(other) {
if (other is! Person) return false;
Person person = other;
return (person.firstName == firstName &&
person.lastName == lastName);
}
}
main() {
var p1 = new Person('bob', 'smith');
var p2 = new Person('bob', 'smith');
var p3 = 'not a person';
var p4 = new Person('jim', 'smith');
print(p1.hashCode == p2.hashCode); // true
print(p1 == p2); // true
print(p1 == p3); // false
print(p1.hashCode == p4.hashCode); // false
print(p1 == p4); // false
}
关于覆写的更多信息请参考下面的 扩展类
四、Extending a class(扩展类)
- 使用
extends
定义子类,supper
引用 超类:
class Television {
void turnOn() {
_illuminateDisplay();
_activateIrSensor();
}
// ...
}
class SmartTelevision extends Television {
void turnOn() {
super.turnOn();
_bootNetworkInterface();
_initializeMemory();
_upgradeApps();
}
// ...
}
- 子类可以覆写实例函数,getter 和 setter。 下面是覆写
Object
类的noSuchMethod()
函数的例子, 如果调用了对象上不存在的函数,则就会触发noSuchMethod()
函 数。
class A {
// 如果你不重写 noSuchMethod 方法, 就用一个不存在的成员,
// 会导致NoSuchMethodError 错误。
void noSuchMethod(Invocation mirror) {
print('You tried to use a non-existent member:' +
'${mirror.memberName}');
}
}
- 使用
@override
注解来 表明你的函数是想覆写超类的一个函数:
class A {
@override
void noSuchMethod(Invocation mirror) {
// ...
}
}
- 使用
noSuchMethod()
函数来实现每个可能的getter
、setter
、 以及其他类型的函数,你可以使用@proxy
注解来避免警告信息:
@proxy
class A {
void noSuchMethod(Invocation mirror) {
// ...
}
}
- 若知道编译时的具体类型,则可以实现这些类来避免警告,和使用
@proxy
效果一样:
class A implements SomeClass, SomeOtherClass {
void noSuchMethod(Invocation mirror) {
// ...
}
}
五、Abstract classes(抽象类)
抽象类 : 一个不能被实例化的类。
使用
abstract
修饰符定义一个 抽象类 ,抽象类通常用来定义接口, 以及部分实现。如果你希望你的抽象类 是可实例化的,则定义一个 工厂构造函数。抽象类通常具有 抽象函数 下面是定义具有抽象函数的抽象类的示例:
// 这个类是抽象类,因此不能被实例化。
abstract class AbstractContainer {
// ...定义构造函数,域,方法...
void updateChildren(); // 抽象方法。
}
- 下面的类不是抽象的,但是定义了一个抽象函数,这样 的类是可以被实例化的:
class SpecializedContainer extends AbstractContainer {
// ...定义更多构造函数,域,方法...
void updateChildren() {
// ...实现 updateChildren()...
}
// 抽象方法造成一个警告,但是不会阻止实例化。
void doSomething();
}
六、Implicit interfaces(隐式接口)
每个类都隐式的定义了一个包含所有实例成员的接口, 并且这个类实现了这个接口。如果你想 创建类 A 来支持 类 B 的API,而不想继承 B 的实现, 则类 A 应该实现 B 的接口。此外,Dart和Java一样只支持单继承。而且Dart中没有和Java一样提供Interface
字段去声明一个接口,如果想使用和Java接口一样的功能可以使用implements
和Mixins
两种方式(Mixins
方式见文末)。
- 一个类可以通过
implements
关键字来实现一个或者多个接口, 并实现每个接口定义的 API。 例如:
// Person类 ,包含 greet() 隐式接口。
class Person {
// 在这个接口中,只有库中可见。
final _name;
// 不在接口中,因为这是个构造函数。
Person(this._name);
// 在接口中。
String greet(who) => 'Hello, $who. I am $_name.';
}
// Person 接口的一个实现。
class Imposter implements Person {
// 我们不得不定义它,但不用它。
final _name = "";
String greet(who) => 'Hi $who. Do you know who I am?';
}
greetBob(Person person) => person.greet('bob');
main() {
print(greetBob(new Person('kathy')));
print(greetBob(new Imposter()));
}
- 下面是实现多个接口 的示例:
class Point implements Comparable, Location {
// ...
}
七、Adding features to a class: mixins(为类添加新的功能)
Mixins: 是一种在多类继承中重用 一个类代码的方法(指能够将另一个或多个类的功能添加到您自己的类中,而无需继承这些类,也就是Dart不支持多继承,却能实现多继承功能的一种方式了)。
使用 with
关键字后面为一个或者多个 mixin 名字来使用 mixin。 下面是示例显示了如何使用 mixin:
class Musician extends Performer with Musical {
// ...
}
class Maestro extends Person with Musical, Aggressive, Demented {
Maestro(String maestroName) {
name = maestroName;
canConduct = true;
}
}
- 定义一个类继承
Object
,该类用abstract class
或者mixin
关键字修饰且没有构造函数, 不能调用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');
}
}
}
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');
}
}
}
再看implements和mixin示例:
class A {
void a() {
print('a');
}
}
class B implements A {
@override
void a() {
print('override a()');
}
}
class C {
void c() {
print('c');
}
}
class E {
String e = 'eeee';
}
class D extends A with C, E {
void c() {
print('c is D');
}
void d() {
c();
}
}
首先看B implements A
,所以此时A相对于B来说就是一个接口,所以他要实现B
中的方法。换句话说,Dart每个类都是接口。
然后看D extends A with C
,D
继承于A
,由于单继承特性,这个时候D
不能再使用extends
关键字继承其他类,但是可以使用with
关键字折叠其他类以实现代码重用。当属性和方法重复时,以当前类为准。比如上面例子调用D
的c()
方法打印的是 c is D。
注意: 从 Dart 1.13 开始, 这两个限制在 Dart VM 上 没有那么严格了:
1、 Mixins 可以继承其他类,不再限制为继承Object
2、 Mixins 可以调用super()
。
3、这种 “super mixins” 还 无法在 dart2js 中使用 并且需要在 dartanalyzer 中使用--supermixin
参数。
详情,请参考 Dart2.1 mixin规范
八、Enumerated types(枚举类型)
枚举类型:通常称之为 enumerations 或者 enums, 是一种特殊的类,用来表现一个固定 数目的常量。
- 使用
enum
关键字来定义枚举类型
enum Color {
red,
green,
blue
}
- 枚举类型中的每个值都有一个 index getter 函数, 该函数返回该值在枚举类型定义中的位置(从 0 开始)。 例如,第一个枚举值的位置为 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
语句中使用枚举。 如果在switch (*e*)
中的 e 的类型为枚举类, 如果你没有处理所有该枚举类型的值的话,则会抛出一个警告:
enum Color {
red,
green,
blue
}
// ...
Color 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'
}
枚举类型具有如下的限制:
1、无法继承枚举类型、无法使用 mixin、无法实现一个枚举类型
2、无法显示的初始化一个枚举类型
九、Class variables and methods(类变量和函数)
使用 static
关键字来实现类级别的变量和函数。
9.1.Static variables(静态变量)
- 静态变量在第一次使用的时候才被初始化。
- 静态变量对于类级别的状态是 非常有用的:
class Color {
static const red = const Color('red'); // 一个恒定的静态变量
final String name; // 一个实例变量。
const Color(this.name); // 一个恒定的构造函数。
}
main() {
assert(Color.red.name == 'red');
}
9.2.Static methods(静态函数)
- 静态函数不再类实例上执行, 所以无法访问
this
。例如:
import 'dart:math';
class Point {
num x;
num 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);
}
}
main() {
var a = new Point(2, 2);
var b = new Point(4, 4);
var distance = Point.distanceBetween(a, b);
assert(distance < 2.9 && distance > 2.8);
}
- 静态函数还可以当做编译时常量使用。例如, 你可以把静态函数当做常量构造函数的参数来使用。
注意:对于通用的或者经常使用的静态函数,考虑 使用顶级方法而不是静态函数。
十、Metadata(元数据)
使用元数据给你的代码添加其他额外信息。 元数据注解是以 @
字符开头,后面是一个编译时 常量(例如 deprecated
)或者 调用一个常量构造函数。
有三个注解所有的 Dart 代码都可以使用: @deprecated
、 @override
、 和 @proxy
。关于 @override
和@proxy
示例请参考上面的扩展类。 下面是使用 @deprecated
的 示例:
class Television {
/// _Deprecated: Use [turnOn] instead._
@deprecated
void activate() {
turnOn();
}
/// Turns the TV's power on.
void turnOn() {
print('on!');
}
}
你还可以定义自己的元数据注解。 下面的示例定义了一个带有两个参数的 @todo
注解:
library todo;
class todo {
final String who;
final String what;
const todo(this.who, this.what);
}
使用 @todo
注解的示例:
import 'todo.dart';
@todo('seth', 'make this do something')
void doSomething() {
print('do something');
}
元数据可以在 library、 class、 typedef、 type parameter、 constructor、 factory、 function、 field、 parameter、或者 variable 声明之前使用,也可以在 import
或者 export
指令之前使用。 使用反射可以在运行时获取元数据信息。