Flutter基础:Dart编程语言——类

Dart作为一门面向对象的编程语言,类充当了一个非常重要的角色。
前面文章也提到过,Dart中一切皆为对象,而每个对象都是一个类的实例,所有的类都继承于Object
而Dart的继承使用了Mixin机制,不过这个机制将不在这篇文章中展开将,而是放到下一篇专门讲解。

实例变量

以下是一个三维坐标点的类,其中有xyz三个实例变量,如下所示:

1
2
3
4
5
class Point {
num x; //声明实例变量x,初始值为null
num y; //声明实例变量y,初始值为null
num z = 0; //声明实例变量z,初始化为0
}

所有的实例变量都会自动生成一个getter方法,没有声明为final的实例变量还会生成一个setter方法。

构造方法

默认构造方法

定义了一个类的时候就会自动生成一个无参的构造方法,如果需要别的参数的构造方法,可以自己定义,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Point {
num x;
num y;
num z = 0;

Point() {
//默认会生成
}
}
main() {
Point p1 = new Point();
p1.x = 1;
p1.y = 2;
p1.z = 3;
}

看到默认会生成一个构造方法,也许java程序员就会想到一个问题,他是不是能重载?
事实上,Dart的方法时不支持重载的,如下所示,是错误的代码示范,注意,是错误的代码示范!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Point {
num x;
num y;
num z = 0;

Point() {

}

//dart不支持重载,所以这里编译必然会报错
Point(num x, num y, num z) {
this.x = x;
this.y = y;
this.z = z;
}
}

那么,如果dart里面需要多个够着方法怎么办?
这就是下面的“命名构造方法”。

命名构造方法

使用命名构造方法可以为一个类创建多个构造方法,还是上面例子,我们只需要为多个构造方法指定一个命名:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Point {
num x;
num y;
num z = 0;

Point() {}

Point.fromXYZ(num x, num y, num z) {
this.x = x;
this.y = y;
this.z = z;
}
}

main() {
Point p1 = new Point();
p1.x = 1;
p1.y = 2;
p1.z = 3;

Point p2 = new Point.fromXYZ(1, 2, 3);
}

上述例子中的p1p2的值时相同的。

另外,dart提供了一个语法糖来进行一些构造方法简写:

如:

1
2
3
4
5
Point.fromXYZ(num x, num y, num z) {
this.x = x;
this.y = y;
this.z = z;
}

可以写成:

1
Point.fromXYZ(this.x, this.y, this.z);

需要特别注意的是:子类不会继承父类的构造方法。子类如果没有定义构造方法,则只有一个默认的构造方法。

调用父类的构造方法

默认情况下,子类的构造方法会自动调用父类的默认构造方法。父类的构造方法在子类构造方法开始执行的位置调用。

如下是定义一个Point的子类ColorPoint的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Point {
num x;
num y;
num z = 0;

Point() {}

Point.fromXYZ(num x, num y, num z) {
this.x = x;
this.y = y;
this.z = z;
print('Point');
}
}

class ColorPoint extends Point {
String color;

ColorPoint.fromXYZAndColor(num x, num y, num z, String color)
: super.fromXYZ(x, y, z) {
this.color = color;
print('ColorPoint');
}
}

main() {
var cp = new ColorPoint.fromXYZAndColor(1, 2, 3, '#000000');
}

那么控制台将输出以下信息:

1
2
Point
ColorPoint

初始化参数列表

如果提供了一个initializer list(初始化参数列表),则初始化参数列表会在父类构造方法执行之前执行,他们之间的执行顺序如下:

  1. 初始化参数列表
  2. 父类构造方法
  3. 主类构造方法

初始化列表非常适合用来设置final变量的值,下面是算出三维坐标点到原点距离的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import 'dart:math';

class Point {
final num x;
final num y;
final num z;
final toZeroDistance;

Point(x, y, z)
: x = x,
y = y,
z = z,
toZeroDistance =
sqrt((sqrt(x * x + y * y) * sqrt(x * x + y * y)) + z * z);
}

main() {
var p = new Point(1, 2, 3);
print(p.toZeroDistance);
}

重定向构造方法

这个很好理解,直接看一下例子就明白了:

1
2
3
4
5
6
7
8
9
10
class Point {
num x;
num y;

//当前主要的构造方法
Point(this.x, this.y);

//如果不传y,默认y等于0,内部调用主要构造方法
Point.alongXAxis(num x) : this(x, 0);
}

常量构造方法

如果你需要提供一个状态不变的对象,并在编译时将这些对象编译为常量。
做到这些,只需要定义一个const构造方法,并且把所有变量声明为final类型。

1
2
3
4
5
6
7
class ImmutablePoint {
final num x;
final num y;
const ImmutablePoint(this.x, this.y);
static final ImmutablePoint origin =
const ImmutablePoint(0, 0);
}

工厂构造方法

如果一个构造方法并不总是返回一个新的对象,这个时候可以使用factory来定义这个构造方法。

以官方文档为例,一个工厂构造方法可能从缓存中获取一个实例返回,或者返回一个新的实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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);
}
}
}

var logger = new Logger('UI');
logger.log('Button clicked');

可以看到上面示例代码中默认构造方法前面还有个factory,这个关键字代表着这是个工厂构造方法。

要注意的是工厂构造方法时没法访问this关键字的,所以上面就有了在类的内部这么调用构造方法的代码:final logger = new Logger._internal(name);,在上面工厂构造方法中,如果缓存中存在传入的name的key值,则取出缓存中的对应value返回。如果缓存中没找到,就会通过命名构造方法来新建一个对象,缓存起来后返回。

类中的方法

实例方法

对象的实例方法可以访问this,这个与大多数编程语言是一致的,这里就不展开细说了。

Getters和Setters方法

getter方法在每一个实例变量都会具有的,并且如果实例变量不是使用final类型的话,还会具有一个setter方法。
但如果要指定变量的settergetter方法的具体逻辑要怎么写?
这里与java的直接setter和getter不太一样,Dart是需要使用setget关键字来定义的,如下例子所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Rectangle {
num left;
num top;
num width;
num height;

Rectangle(this.left, this.top, this.width, this.height);

num get right {
return left + width;
}

void set right(num value) {
left = value - width;
}

num get bottom {
return top + height;
}

void 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);
}

官方文档使用了=>写法,我为了更清晰展示setget关键字,而将它改成了传统写法。

抽象方法

java中在抽象类中使用abstract关键字指定方法为抽象方法,但在Dart中不一样的是,Dart只要类使用了abstract定义,那么它的方法只需要以一个分号来代替方法体,就代表这个方法为抽象方法。

如下例子所示:

1
2
3
4
5
6
7
8
9
abstract class Bird {
void fly();
}

class Sparrow extends Bird {
void fly() {

}
}

可覆写操作符

Dart中有一些操作符也是可以覆写的,在一个具体类中可以通过覆写特定的操作符实现特定的功能。
可以覆写的操作符及其原本对应的功能说明如下:

操作符 说明
< 小于
> 大于
<= 小于和等于
>= 大于和等于
-
+
/
~/ 整除
*
% 取余
\
^ 异或
&
<< 左移
>> 右移
[] 访问list
[]= 赋值list
~ 一元位补码
== 相等比较

抽象类

抽象类使用abstract关键字定义,是不能被实例化的,通常用来定义接口以及部分实现。
但与其他语言不太一样的地方是,抽象方法也可以定义在非抽象类中,如下例子所示:

1
2
3
4
5
6
7
8
9
10
11
abstract class Bird {
void fly();

}

class Sparrow extends Bird {
void fly() {

}
void sleep();
}

隐式接口

每个类都隐式定义了一个包含所有实例成员的接口,并且这个类实现了这个接口。如果你想创建类A来支持B类的API,而不像继承B的实现,则A应该实现B的接口。一个简单的例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class B {
final _tag;

B(this._tag);

String showTag(from) => 'This class tag is $_tag, show from $from';
}

class A implements B {
final _tag;

A(this._tag);

String showTag(from) => 'This class tag is $_tag, not show from $from';
}

main() {
print(new B('B_TAG').showTag('B'));
print(new A('A_TAG').showTag('B'));
}

类的继承

继承使用extends关键字。
如下例子所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Point {
num x;
num y;
num z = 0;

Point() {}

Point.fromXYZ(num x, num y, num z) {
this.x = x;
this.y = y;
this.z = z;
print('Point');
}
}

class ColorPoint extends Point {
String color;

ColorPoint.fromXYZAndColor(num x, num y, num z, String color)
: super.fromXYZ(x, y, z) {
this.color = color;
print('ColorPoint');
}
}

使用super关键字来引用父类,使用@override注解来表明想覆写父类中的方法,示例代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import 'dart:math';

class Point {
num x;
num y;
num z = 0;

Point() {}

Point.fromXYZ(num x, num y, num z) {
this.x = x;
this.y = y;
this.z = z;
print('Point');
}

num calDistance(){
return sqrt((sqrt(x * x + y * y) * sqrt(x * x + y * y)) + z * z);
}
}

class ColorPoint extends Point {
String color;

ColorPoint.fromXYZAndColor(num x, num y, num z, String color)
: super.fromXYZ(x, y, z) {
this.color = color;
print('ColorPoint');
}

@override
num calDistance(){
return super.calDistance();
}
}

main() {
var cp = new ColorPoint.fromXYZAndColor(1, 2, 3, '#000000');
print(cp.calDistance());
}

枚举

Dart中使用enum关键字来定义枚举类型,一个简单的枚举定义如下:

1
2
3
4
5
enum Color {
red,
green,
blue
}

通过index的getter方法可以获得该枚举在定义中的位置,这个位置的下标是从0开始的。
枚举的values常量可以返回所有枚举值:

1
List<Color> colors = Color.values;

在switch语句中也可以使用枚举。

但枚举具有以下限制:

  • 无法继承枚举类型、无法使用mixin、无法实现一个枚举类型
  • 无法显式初始化一个枚举类型

静态变量和方法

静态变量和方法使用static关键字来指定,也成为类变量和方法。

静态变量需要注意的是只有类在第一次的时候才会被初始化。

静态方法因为不是在类的实例上执行的,所以方法体里面无法访问this。

注意: 对于通用的或者经常使用的静态方法,考虑使用顶级方法而不是静态方法。

文章作者: Kevin Wu
文章链接: https://kevinwu.cn/p/866bb091/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 KevinWu.CN
支付宝打赏