接 Dart 语法学习笔记一,来继续进行 Dart 语法的学习。
类是面向对象语言中一个比较重要的概念,在日常的开发过程中也是使用最多的一个概念。下面是关于类的一些语法知识。
类
Dart 是一种基于类和 mixin 继承机制的面向对象的语言。Dart 中所有类都继承自 Object
。通过构造函数,可以为类实例化一个对象。构造函数的名字可以是 ClassName
或者 ClassName.identifier
。对象由方法和实例变量组成,我们可以使用 .
访问对象的成员变量和方法。
// 通过 构造函数 初始化实例对象
var p1 = Point(2, 2);
// 为实例的变量 y 设置值
p.y = 3;
// 获取变量 y 的值。
assert(p.y == 3);
// 调用 p 的 distanceTo() 方法。
num distance = p.distanceTo(Point(4, 4));
使用 ?.
来代替 .
, 可以避免因为左边对象可能为 null , 导致的异常。
// 如果 p 为 non-null,设置它变量 y 的值为 4。
p?.y = 4;
作者注:这个类似 swift 语法中的 可选(optional)
特性。
常量构造函数
使用常量构造函数,在构造函数名之前加 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 关键字。
const pointAndLine = const {
'point': const [const ImmutablePoint(0, 0)],
'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};
保留第一个 const 关键字,其余的可以全部省略。
// 仅有一个 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)); // 两者不是同一个实例!
获取对象类型
使用对象的 runtimeType
属性, 可以在运行时获取对象的类型, 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 方法。 非 final
的实例变量同样会生成隐式 setter 方法。
class Point {
num x;
num y;
}
void main() {
var point = 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.
}
如果在声明时进行了实例变量的初始化, 那么初始化值会在实例创建时赋值给变量, 该赋值过程在构造函数及其初始化列表执行之前。
构造函数
通过创建一个与其类同名的函数来声明构造函数 (另外,还可以附加一个额外的可选标识符,如 命名构造函数 中所述)。
class Point {
num x, y;
Point(num x, num y) {
// 还有更好的方式来实现下面代码,敬请关注。
this.x = x;
this.y = y;
}
}
注:使用 this
关键字引用当前实例。
通常模式下,会将构造函数传入的参数的值赋值给对应的实例变量, Dart 自身的语法糖精简了这些代码。
class Point {
num x, y;
// 在构造函数体执行前,
// 语法糖已经设置了变量 x 和 y。
Point(this.x, this.y);
}
子类不会继承父类的构造函数。 子类不声明构造函数,那么它就只有默认构造函数 (匿名,没有参数) 。
在没有声明构造函数的情况下, Dart 会提供一个默认的构造函数。 默认构造函数没有参数并会调用父类的无参构造函数。
命名构造函数
使用命名构造函数可为一个类实现多个构造函数, 也可以使用命名构造函数来更清晰的表明函数意图:
class Point {
num x, y;
Point(this.x, this.y);
// 命名构造函数
Point.origin() {
x = 0;
y = 0;
}
}
注:构造函数不能够被继承, 这意味着父类的命名构造函数不会被子类继承。 如果希望使用父类中定义的命名构造函数创建子类, 就必须在子类中实现该构造函数。
调用父类非默认构造函数
默认情况下,子类的构造函数会自动调用父类的默认构造函数(匿名,无参数)。 父类的构造函数在子类构造函数体开始执行的位置被调用。 如果提供了一个 initializer list (初始化参数列表), 则初始化参数列表在父类构造函数执行之前执行。 执行顺序如下:
1、initializer list (初始化参数列表)
2、superclass’s no-arg constructor (父类的无名构造函数)
3、main class’s no-arg constructor (当前类的无名构造函数)
如果父类中没有匿名无参的构造函数, 则需要手动调用父类的其他构造函数。 在当前构造函数冒号 (:) 之后,函数体之前,声明调用父类构造函数。
示例:Employee 类的构造函数调用了父类 Person 的命名构造函数。
class Person {
String firstName;
Person.fromJson(Map data) {
print('in Person');
}
}
class Employee extends Person {
// Person does not have a default constructor;
// you must call 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(getDefaultData());
}
初始化参数列表
除了调用超类构造函数之外, 还可以在构造函数体执行之前初始化实例变量。 各参数的初始化用 ,
分隔。
// 在构造函数体执行之前,
// 通过初始列表设置实例变量。
Point.fromJson(Map<String, num> json)
: x = json['x'],
y = json['y'] {
print('In Point.fromJson(): ($x, $y)');
}
使用初始化列表可以很方便的设置 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);
}
重定向构造函数
有时构造函数的唯一目的是重定向到同一个类中的另一个构造函数。 重定向构造函数的函数体为空, 构造函数的调用在冒号 : 之后。
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 关键字。 例如,一个工厂构造函数可能会返回一个 cache 中的实例, 或者可能返回一个子类的实例。
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);
}
}
Getter 和 Setter
Getter 和 Setter 是用于对象属性读和写的特殊方法。使用 get
和 set
关键字实现 Getter 和 Setter ,能够为实例创建额外的属性。
class Rectangle {
num left, top, width, height;
Rectangle(this.left, this.top, this.width, this.height);
// 定义两个计算属性: right 和 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;
}
void main() {
var rect = Rectangle(3, 4, 20, 15);
assert(rect.left == 3);
rect.right = 12;
assert(rect.left == -8);
}
抽象方法
只定义接口不进行实现,而是留给其他类去实现,这样的方法就叫抽象方法。抽象方法只存在于 抽象类 中。
abstract class Doer {
// 定义实例变量和方法 ...
void doSomething(); // 定义一个抽象方法。
}
class EffectiveDoer extends Doer {
void doSomething() {
// 提供方法实现,所以这里的方法就不是抽象方法了...
}
}
抽象类
使用 abstract
修饰符来定义 抽象类 — 抽象类不能实例化。 抽象类通常用来定义接口,以及部分实现。 如果希望抽象类能够被实例化,那么可以通过定义一个 工厂构造函数 来实现。
// 这个类被定义为抽象类,
// 所以不能被实例化。
abstract class AbstractContainer {
// 定义构造行数,字段,方法...
void updateChildren(); // 抽象方法。
}
每个类都隐式的定义了一个接口,接口包含了该类所有的实例成员及其实现的接口。 如果要创建一个 A 类,A 要支持 B 类的 API ,但是不需要继承 B 的实现, 那么可以通过 A 实现 B 的接口。
一个类可以通过 implements
关键字来实现一个或者多个接口, 并实现每个接口要求的 API。
// person 类。 隐式接口里面包含了 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();
}
// ···
}
重写类成员
子类可以重写实例方法,getter 和 setter。 可以使用 @override
注解指出想要重写的成员。
class SmartTelevision extends Television {
@override
void turnOn() {
...
}
}
枚举类型
使用 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 )。
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: // 没有这个,会看到一个警告。
print(aColor); // 'Color.blue'
}
Mixin
Mixin 是复用类代码的一种途径, 复用的类可以在不同层级,之间可以不存在继承关系。
通过 with
后面跟一个或多个混入的名称,来使用 Mixin , 下面的示例演示了两个使用 Mixin 的类:
class Musician extends Performer with Musical {
// ···
}
class Maestro extends Person
with Musical, Aggressive, Demented {
Maestro(String maestroName) {
name = maestroName;
canConduct = true;
}
}
通过创建一个继承自 Object
且没有构造函数的类,来实现 一个 Mixin 。 如果 Mixin 不希望作为常规类被使用,使用关键字 mixin
替换 class 。
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');
}
}
}
指定只有某些类型可以使用的 Mixin(比如, Mixin 可以调用 Mixin 自身没有定义的方法), 使用 on 来指定可以使用 Mixin 的父类类型。
mixin MusicalPerformer on Musician {
// ···
}
类变量和方法
使用 static
关键字实现类范围的变量和方法。
静态变量
静态变量(类变量)对于类级别的状态是非常有用的。
class Queue {
static const initialCapacity = 16;
// ···
}
void main() {
assert(Queue.initialCapacity == 16);
}
静态变量只到它们被使用的时候才会初始化。
静态方法
静态方法(类方法)不能在实例上使用,因此它们不能访问 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);
}