Dart tour: 9、类
Dart是支持基于mixin继承机制的面向对象语言,所有对象都是一个类的实例,而除了 Null 以外的所有的类都继承自 Object 类。基于mixin的继承扩展是一种在不更改类或创建子类的情况下向类添加功能的方式。
类的成员#
对象的成员由函数和数据(即方法和实例变量)组成。方法的调用要通过对象来完成,这种方式可以访问对象的函数和数据。
实例变量#
未初始化的实例变量其值均为 null, 且实例变量也支持 final、late 等修饰符。
构造函数#
当且仅当命名冲突时使用
this关键字才有意义,否则Dart会忽略this关键字。
可以使用构造函数来创建一个对象。从
Dart 2开始,new关键字是可选的。
构造函数是一个与类名一样的函数(对于命名式构造函数 还可以添加额外的标识符),其用于创建一个类的实例。
默认构造函数#
如果没有声明构造函数,那么 Dart 会自动生成一个无参数的构造函数并且该构造函数会调用其父类的无参数构造方法。
构造函数不被继承#
子类不会继承父类的构造函数,如果子类没有声明构造函数,那么只会有一个默认无参数的构造函数。
命名式构造函数#
可以为一个类声明多个命名式构造函数来表达更明确的意图:
调用父类非默认构造函数#
传递给父类构造函数的参数不能使用
this关键字,因为在参数传递的这一步骤,子类构造函数尚未执行,子类的实例对象也就还未初始化,因此所有的实例成员都不能被访问,但是类成员可以。
默认情况下,子类的构造函数会调用父类的匿名无参构造函数,并且该调用会在子类构造函数的函数体代码执行前,如果子类构造函数还有一个初始化列表,那么该初始化列表会在调用父类的该构造函数之前被执行,总的来说,这三者的调用顺序如下:
- 初始化列表
- 父类的无参构造函数
- 当前类的构造函数
如果父类没有匿名无参构造函数,那么子类必须调用父类的其中一个构造函数,为子类的构造函数指定一个父类的构造函数需在构造函数体前使用(:)指定。
因为参数会在子类构造函数被执行前传递给父类的构造函数,因此该参数也可以是一个表达式,比如一个函数。
初始化列表#
初始化列表表达式
=右边的语句不能使用this关键字
除了调用父类构造函数之外,还可以在构造函数体执行之前初始化实例变量。每个实例变量之间使用逗号分隔。
重定向构造函数#
有时候类中的构造函数仅用于调用类中其它的构造函数,此时该构造函数没有函数体,只需在函数签名后使用(:)指定需要重定向到的其它构造函数。
常量构造函数#
如果类生成的对象都是不变的,可以在生成这些对象时就将其变为编译时常量。可以在类的构造函数前加上 const 关键字并确保所有实例变量均为 final 来实现该功能。
使用常量构造函数,在构造函数名之前加 const 关键字,来创建编译时常量时, 两个使用相同构造函数相同参数值构造的编译时常量是同一个对象。
在常量上下文场景中,通常可以省略掉构造函数或字面量前的 const 关键字。
工厂构造函数#
在工厂构造函数中无法访问
this。
使用 factory 关键字标识类的构造函数将会令该构造函数变为工厂构造函数,这将意味着使用该构造函数构造类的实例时并非总是会返回新的实例对象。例如,工厂构造函数可能会从缓存中返回一个实例,或者返回一个子类型的实例。
方法#
方法是为对象提供行为的函数。
实例方法#
对象的实例方法可以访问实例变量和 this。
操作符#
运算符是有着特殊名称的实例方法。 Dart 允许使用以下名称定义运算符
<+|[]>/^[]=<=~/&~>=*<<==–%>>
使用 operator 标识符表示重写操作符。
Getter 和 Setter#
Getter 和 Setter 是一对用来读写对象属性的特殊方法,上面说过实例对象的每一个属性都有一个隐式的 Getter 方法,如果为非 final 属性的话还会有一个 Setter 方法,可以使用 get 和 set 关键字为额外的属性添加 Getter 和 Setter 方法。
使用 Getter 和 Setter 的好处是,可以先使用实例变量,过一段时间过再将它们包裹成方法且不需要改动任何代码,即先定义后更改且不影响原有逻辑。
抽象方法#
实例方法、Getter 方法以及 Setter 方法都可以是抽象的,定义一个接口方法而不去做具体的实现让实现它的类去实现该方法,抽象方法只能存在于抽象类中。直接使用分号(;)替代方法体即可声明一个抽象方法。
抽象类#
使用关键字 abstract 标识类可以让该类成为抽象类,抽象类将无法被实例化。抽象类常常会包含抽象方法。
抽象类常用于声明接口方法、有时也会有具体的方法实现。如果想让抽象类同时可被实例化,可以为其定义工厂构造函数。
隐式接口#
如果需要实现多个类接口,可以使用逗号分割每个接口类:
class Point implements Comparable, Location {...}
每一个类都隐式地定义了一个接口并实现了该接口,这个接口包含所有这个类的实例成员以及这个类所实现的其它接口。
如果想要创建一个 A 类支持调用 B 类的 API 且不想继承 B 类,则可以实现 B 类的接口。
一个类可以通过关键字 implements 来实现一个或多个接口并实现每个接口定义的 API:
拓展类#
使用 extends 关键字来创建一个子类,并可使用 super 关键字引用一个父类。
子类可以重写父类的实例方法(包括 操作符)、 Getter 以及 Setter 方法。可以使用 @override 注解来表示重写了一个成员。
如果重写
==操作符,必须同时重写对象hashCode的Getter方法。
covariant 类型约束#
在重写时,可以使用 covariant 关键字来缩小代码中那些符合的方法参数或实例变量的类型。
noSuchMethod 方法#
如果调用了对象上不存在的方法或实例变量将会触发 noSuchMethod 方法,可以重写 noSuchMethod 方法来追踪和记录这一行为。声明的变量需要是 dynamic 类型的才可以触发。
扩展方法#
扩展方法是向现有库添加功能的一种方式。扩展方法不仅可以定义方法,还可以定义其他成员,例如 getter,setter 和operator。
以下是使用对字符串进行操作的扩展(名为 NumberParsing)来实现扩展方法 parseInt()的方法:
类变量和方法#
静态变量#
静态变量在其首次被使用的时候才被初始化。
使用关键字 static 可以声明类变量或类方法。静态变量(即类变量)常用于声明类范围内所属的状态变量和常量。
静态方法#
可以将静态方法作为编译时常量。例如,可以将静态方法作为一个参数传递给一个常量构造函数。
静态方法(即类方法)不能对实例进行操作,因此不能使用 this。但是可以访问静态变量。
可调用类#
通过实现类的 call() 方法,允许使用类似函数调用的方式来使用该类的实例。
在下面的示例中,WannabeFunction 类定义了一个 call() 函数,函数接受三个字符串参数,函数体将三个字符串拼接,字符串间用空格分割,并在结尾附加了一个感叹号。