一、Object(对象)
- Dart 是一个面向对象编程语言。
- Dart支持基于 mixin 的继承机制(相当于多继承,但不是多继承,Dart语言是单继承,mixin只是实现类似多继承的一种方式)。
- 每个对象都是一个类的实例,所有的类都继承于 Object.。
- 所有的类有同一个基类:
Object
。 - 使用
new
关键字和构造函数来创建新的对象。 构造函数名字可以为 ClassName 或者 ClassName.identifier。例如:
var jsonData = JSON.decode('{"x":1, "y":2}');
// Create a Point using Point().
var p1 = new Point(2, 2);
// Create a Point using Point.fromJson().
var p2 = new Point.fromJson(jsonData);
- 使用点
.
来引用对象的变量或者方法:
var p = new Point(2, 2);
// Set the value of the instance variable y.
p.y = 3;
// Get the value of y.
assert(p.y == 3);
// Invoke distanceTo() on p.
num distance = p.distanceTo(new Point(4, 4));
- 使用
?.
来替代.
可以避免当左边对象为null
时候 抛出异常(有点类似swift语言)
// If p is non-null, set its y value to 4.
p?.y = 4;
- 有些类提供了常量构造函数。使用常量构造函数 可以创建编译时常量,要使用常量构造函数只需要用
const
替代new
即可
var p = const ImmutablePoint(2, 2);
- 两个一样的编译时常量其实是 同一个对象
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);
assert(identical(a, b)); // They are the same instance!
- 可以使用 Object 的
runtimeType
属性来判断实例 的类型,该属性 返回一个 Type 对象。
var a = 'test';
var b = 5;
var c = [56,3,4,6,0];
var d = {
'key1':'value1',
'key2':'value2'
};
// The type of a is String
print('The type of a is ${a.runtimeType}');
// The type of a is int
print('The type of a is ${b.runtimeType}');
// The type of a is List
print('The type of a is ${c.runtimeType}');
// The type of a is _InternalLinkedHashMap
print('The type of a is ${d.runtimeType}');
二、Instance variables(实例化变量)
- 在类定义中,所有没有初始化的变量都会被初始化为
null
。
class Point {
num x; // Declare instance variable x, initially null.
num y; // Declare y, initially null.
num z = 0; // Declare z, initially 0.
}
main() {
/* 每个实例变量都会自动生成一个 getter 方法(隐含的)。
Non-final 实例变量还会自动生成一个 setter 方法。
*/
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.
}
三、Constructors(构造函数)
注意:Constructors aren’t inherited(构造函数不会继承)
子类不会继承超类的构造函数。 子类如果没有定义构造函数,则只有一个默认构造函数 (没有名字没有参数)。如果你希望子类也有超类一样的命名构造函数, 你必须在子类中自己实现该构造函数。
3.1.Default constructors(默认构造函数)
如果你没有定义构造函数,则会有个默认构造函数。 默认构造函数没有参数,并且会调用超类的没有参数的构造函数。
- 声明一个和类名相同的函数,来作为类的构造函数。
class Point {
num x;
num y;
Point(num x, num y) {
// There's a better way to do this, stay tuned.
this.x = x;
this.y = y;
}
}
- this 关键字指当前的实例。
注意: 只有当名字冲突的时候才使用
this
。否则的话, Dart 代码风格样式推荐忽略this
。
class Point {
num x;
num y;
// Point(num x, num y) {
// // There's a better way to do this, stay tuned.
// this.x = x;
// this.y = y;
// }
Point(num param1, num param2) {
// 实例变量和参数名不冲突市,推荐忽略 this
x = param1;
y = param2;
}
}
- 把构造函数参数赋值给实例变量的场景太常见了, Dart 提供了一个语法糖来简化这个操作:
class Point {
num x;
num y;
// Syntactic sugar for setting x and y
// before the constructor body runs.
Point(this.x, this.y);
}
3.2.Named constructors(命名构造函数)
使用命名构造函数可以为一个类实现多个构造函数, 或者使用命名构造函数来更清晰的表明你的意图:
class Point {
num x;
num y;
Point(this.x, this.y);
// Named constructor
Point.fromJson(Map json) {
x = json['x'];
y = json['y'];
}
}
3.3.Invoking a non-default superclass constructor(调用超类构造函数)
默认情况下,子类的构造函数会自动调用超类的 无名无参数的默认构造函数。 超类的构造函数在子类构造函数体开始执行的位置调用。 如果提供了一个 initializer list(初始化参数列表) ,则初始化参数列表在超类构造函数执行之前执行。 下面是构造函数执行顺序:
- initializer list(初始化参数列表)
- superclass’s no-arg constructor(超类的无名构造函数)
- main class’s no-arg constructor(主类的无名构造函数)
如果超类没有无名无参数构造函数, 则你需要手工的调用超类的其他构造函数。 在构造函数参数后使用冒号 (:
) 可以调用 超类构造函数。
下面的示例中,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({});
// Prints:
// 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(findDefaultData());
}
注意: 1. 如果在构造函数的初始化列表中使用
super()
,需要把它放到最后。 2.调用超类构造函数的参数无法访问this
。 例如,参数可以为静态函数但是不能是实例函数。
- 在构造函数体执行之前除了可以调用超类构造函数之外,还可以 初始化实例参数。 使用逗号分隔初始化表达式。
如Initializer list(初始化列表):
class Point {
num x;
num y;
Point(this.x, this.y);
// 构造函数体执行之前,初始化实例参数
Point.fromJson(Map jsonMap)
: x = jsonMap['x'],
y = jsonMap['y'] {
print('In Point.fromJson(): ($x, $y)');
}
}
警告: 初始化表达式等号右边的部分不能访问
this
。
3.4.Redirecting constructors(重定向构造函数)
有时候一个构造函数会调动类中的其他构造函数。 一个重定向构造函数是没有代码的,在构造函数声明后,使用 :
(冒号)调用其他构造函数。
class Point {
num x;
num y;
// Point类的主构造函数
Point(this.x, this.y);
// 重定向构造函数,指向主构造函数,函数体为空
Point.alongXAxis(num x) : this(x, 0);
}
3.5.Constant constructors(常量构造函数)
如果你的类提供一个状态不变的对象,你可以把这些对象 定义为编译时常量。要实现这个功能,需要定义一个 const
构造函数, 并且声明所有类的变量为 final
。
class ImmutablePoint {
final num x;
final num y;
const ImmutablePoint(this.x, this.y);
static final ImmutablePoint origin =
const ImmutablePoint(0, 0);
}
3.6.Factory constructors(工厂方法构造函数)
如果一个构造函数并不总是返回一个新的对象,则使用 factory
来定义 这个构造函数。例如,一个工厂构造函数 可能从缓存中获取一个实例并返回,或者 返回一个子类型的实例。
下面代码演示工厂构造函数 如何从缓存中返回对象。
class Logger {
final String name;
bool mute = false;
// _cache 是一个私有库,幸好名字前有个 _ 。
static final Map _cache =
{};
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);
}
}
}
注意: 工厂构造函数无法访问
this
。
- 使用
new
关键字来调用工厂构造函数。
var logger = new Logger('UI');
logger.log('Button clicked');
四、Callable classes(可调用的类)
如果 Dart 类实现了 call()
函数则 可以当做方法来调用。
在下面的示例中,WannabeFunction 类定义了一个 call()
方法,该方法有三个字符串参数,并且返回三个字符串 串联起来的结果。
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');
}
关于把类当做方法使用的更多信息,请参考 Emulating Functions in Dart(在 Dart 中模拟方法)。
五、Typedefs(别名)
在 Dart 语言中,方法也是对象。 使用 typedef
, 或者 function-type alias 来为方法类型命名, 然后可以使用命名的方法。 当把方法类型赋值给一个变量的时候,typedef
保留类型信息。
下面的代码没有使用 typedef
:
class SortedCollection {
Function compare;
SortedCollection(int f(Object a, Object b)) {
compare = f;
}
}
// 初始化,终止实现(不实现).
int sort(Object a, Object b) => 0;
main() {
SortedCollection coll = new SortedCollection(sort);
// 我们只知道 compare 是一个 Function 类型,
// 但是不知道具体是何种 Function 类型?
assert(coll.compare is Function);
}
当把 f
赋值给 compare
的时候, 类型信息丢失了。 f 的类型是 (Object
, Object
) → int
(这里 → 代表返回值类型), 当然该类型是一个 Function。如果我们使用显式的名字并保留类型信息, 开发者和工具可以使用 这些信息:
typedef int Compare(Object a, Object b);
class SortedCollection {
Compare compare;
SortedCollection(this.compare);
}
// Initial, broken implementation.
int sort(Object a, Object b) => 0;
main() {
SortedCollection coll = new SortedCollection(sort);
assert(coll.compare is Function);
assert(coll.compare is Compare);
}
注意: 目前,
typedefs
只能使用在 function 类型上,但是将来 可能会有变化。
由于 typedefs
只是别名,他们还提供了一种 判断任意 function 的类型的方法。例如:
typedef int Compare(int a, int b);
int sort(int a, int b) => a - b;
main() {
assert(sort is Compare); // True!
}