类(Class)是面向对象程序设计,实现信息封装的基础。类是一种用户定义的类型。每个类包含数据说明和一组操作数据或传递消息的函数。类的实例称为对象。

Dart的类与其它语言都有很大的区别,比如在dart的类中可以有无数个构造函数,可以重写类中的操作符,有默认的构造函数,由于dart没有接口,所以dart的类也是接口,因此你可以将类作为接口来重新实现

访问属性与方法

对象是由函数和属性组成,你可以调用对象上的方法或者访问属性。对象上的属性如果没有被赋值,Dart默认会将属性初始化为Null,

import 'dart:math';

class Point {
  int y; // 将被初始化为null
  int x;
  
  double get magnitude => sqrt(x * x + y * y);
  double distanceTo() {
    var dx = x - this.x;
    var dy = y - this.y;
    return sqrt(dx * dx + dy * dy);
  }
}

main(List<String> args) {
  var p = Point();

// 改变属性
  p.x = 3;
  p.y = 4;

// 访问属性y
  assert(p.x == 3);

// 调用P上的方法
  num distance = p.distanceTo();
  print(distance);
}

当p为null时,可以使用 ?. 替代 . 避免发生异常

p?.y = 4;

构造函数

在Dart中构造函数的名称可以是类名 ClassName  或者类名和标识符 ClassName.identifier类名和标识符的构造函数可以有无数多个,但类名的构造函数只能有一个下面的代码使用类名 Point 创建构造函数

import 'dart:math';

class Point {
  int y;
  int x;

  // 类名构造函数
  Point(num x, num y) {
    this.x = x;
    this.y = y;
  }
  // .....
}

在构造函数里初始化成员属性是很常见的事情,因此Dart开发了新的语法糖来简化这种操作,比如将Point的类名构造构造函数改写成

class Point {
  num x, y;

  // 注意x,y的赋值会在构造函数执行之前完成.
  Point(this.x, this.y);
}

Dart的第一个版本实例化对象需要new关键字,但在Dart 2之后就去掉了new关键字

main(List<String> args) {
  // 调用类名构造函数
  Point point1 = Point(3,4);
  print(point1.x);
}
默认构造函数

如果类中没有声明构造函数,Dart会提供一个默认的构造函数。这个默认的构造函数会调用父类的默认构造函数,并且该构造函数是没有参数的

构造函数不能被继承

子类不能继承造函数。声明没有构造函数的子类只有默认的(无参数,无名称)构造函数

命名构造函数

使用命名构造函数可以为类提供多个构造函数,按官方的说法就是提供额外的清晰度

class Point {
  num x, y;

  Point(this.x, this.y);

  // 命名构造函数
  Point.origin() {
    x = 0;
    y = 0;
  }
}

调用命名构造函数

main(List<String> args) {
  // 调用命名构造函数
  Point point1 = Point.origin();
}

之前说过,构造函数是不嫩被继承的,这意味着父类的命名构造函数也不能被子类继承。如果你想在一个已经定义命名构造函数的父类中通过继承创建一个子类,你必须在子类中实现构造函数

调用非默认的父类构造函数

在默认情况下,子类可以调用父类的未命名,无参数的构造函数即默认构造函数。父类的构造函数会在子类的构造函数之前开始调用,如果子类中存在需要初始化的成员属性,则可以先初始化子类成员属性,再调用父类的构造函数,执行过程如下

  1. 初始化子类成员属性
  2. 调用父类构造函数
  3. 子类构造函数

如果父类中没有默认的构造函数,你必须手动调用父类的构造函数,在子类的构造函数体之前通过 : 指定调用父类构造函数,示例如下

// Person类中没有一个无参数,未命名的构造函数
class Person {
  String firstName;
  // 命名构造函数
  Person.fromJson(Map data) {
    print('in Person');
  }
}

class Employee extends Person {
  // 你必须调用父类的super.fromJson(data).
  
  Employee.fromJson(Map data) : super.fromJson(data) {
    print('in Employee');
  }
}

main() {
  var emp = new Employee.fromJson({});

获取对象类型

如果想获得对象运行时的类型可以使用runtimeType ,这个对象属性将会返回对象本身的类型

print('p1对象类型是 ${p1.runtimeType}');

Getters and setters

Getters和setters是读取和修改对象的特定方法,每次调用对象的属性时,Dart都会隐式的调用一次getter方法,这允许你可以在修改或者读取对象属时做一些操作,通过 getset 关键词重写对象的默认行为

class Rectangle {
  num left, top, width, height;

  Rectangle(this.left, this.top, this.width, this.height);
  // 重写right属性
  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);
}

抽象方法

实例getter的setter方法是抽象的方法,它定义在接口中,但没有被实现。抽象的方法仅存在抽象类中

abstract class Doer {
  // 在这里定义实例的属性和方法

  void doSomething(); // 定义抽象方法.
}

class EffectiveDoer extends Doer {
  void doSomething() {
    // 实现抽象方法
  }
}

抽象类

使用 abstract 修改器可以定义一个抽象类。抽象类是不能被实例化的,但对于定义接口是非常有用的,如果你想实例化抽象类,你必须实现抽象类,才能被实例化

// 这是一个抽象类不能被实例化
abstract class AbstractContainer {
  // 在这里可以定义属性,方法,构造函数

  void updateChildren(); // 抽象方法.
}

隐式的接口

每个类都是都是隐式的接口,包括类的方法和属性。如果你想创建一个类A不继承B的实现,可以实现B的接口来创建类A。一个类允许通过implements 关键词可以实现多个接口

// 每个类都是一个隐式的接口,所以Person类也是个接口,包括成员属性和方法.
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();
  }
  // ···
}
重写成员

可以使用 @override 关键字,子类可以重写实例的方法,getters和setters

class SmartTelevision extends Television {
  @override
  void turnOn() {...}
  // ···
}
重写操作符

可以重写下表中在对象上的操作符


<	+	|	[]
>	/	^	[]=
<=	~/	&	~
>=	*	<<	==
–	%	

可以重写操作符,下面是重写加减操作符的示例

class Vector {
  final int x, y;

  Vector(this.x, this.y);

  Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
  Vector operator -(Vector v) => Vector(x - v.x, y - v.y);

  // Operator == and hashCode not shown. For details, see note below.
  // ···
}

void main() {
  final v = Vector(2, 3);
  final w = Vector(2, 2);

  assert(v + w == Vector(4, 5));
  assert(v - w == Vector(0, 1));
}

如果你需要重写 == 操作符,请参考操作符教程

noSuchMethod()

当用户调用你定义的类中不存在的属性与方法时,可以做出一些响应,通过重写noSuchMethod()

class A {
  @override
  void noSuchMethod(Invocation invocation) {
    print('You tried to use a non-existent member: ' +
        '${invocation.memberName}');
  }
}

枚举类

枚举类是一种特殊类,用于表示固定数量的常量值。

enum Color { red, green, blue }

枚举中的每个值都有一个索引,它返回枚举声明中值的位置。例如,第一个值具有索引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);

枚举类不会像正常类一样可以被继承,重写等等,它具有以下限制

  • 不能继承,重新实现,在Minix使用
  • 不能实例化枚举类
静态变量
class Queue {
  static const initialCapacity = 16;
  // ···
}

void main() {
  assert(Queue.initialCapacity == 16);
}
静态方法
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);
}

如果你要在应用中大范围使用某个静态方法,Dart建议静态函数改为顶级函数

import 'dart:math';

class Point {
  num x, y;
  Point(this.x, this.y);
}
// 顶级函数
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 = distanceBetween(a, b);
  assert(2.8 < distance && distance < 2.9);
  print(distance);
}