类 - Classes

Dart 是一个面向对象编程语言,同时支持基于 mixin 的继承机制。 每个对象都是一个类的实例,所有的类都继承于 Object.。 基于 Mixin 的继承 意味着每个类(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 时候 抛出异常:

// 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 对象。

print('The type of a is ${a.runtimeType}');

下面介绍如何实现一个类。

Instance variables

下面是如何定义实例变量的示例:

class Point {
  num x; // Declare instance variable x, initially null.
  num y; // Declare y, initially null.
  num z = 0; // Declare z, initially 0.
}

所有没有初始化的变量值都是 null。

每个实例变量都会自动生成一个 getter 方法(隐含的)。 Non-final 实例变量还会自动生成一个 setter 方法。详情, 参考 Getters and setters。

class Point {
  num x;
  num y;
}

main() {
  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

定义一个和类名字一样的方法就定义了一个构造函数 还可以带有其他可选的标识符,详情参考 Named 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。

由于把构造函数参数赋值给实例变量的场景太常见了, Dart 提供了一个语法糖来简化这个操作:

class Point {
  num x;
  num y;

  // Syntactic sugar for setting x and y
  // before the constructor body runs.
  Point(this.x, this.y);
}

Default constructors(默认构造函数)

如果你没有定义构造函数,则会有个默认构造函数。 默认构造函数没有参数,并且会调用超类的 没有参数的构造函数。

Constructors aren’t inherited(构造函数不会继承)

子类不会继承超类的构造函数。 子类如果没有定义构造函数,则只有一个默认构造函数 (没有名字没有参数)。

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'];
  }
}

注意:构造函数不能继承,所以超类的命名构造函数 也不会被继承。如果你希望 子类也有超类一样的命名构造函数, 你必须在子类中自己实现该构造函数。

Invoking a non-default superclass constructor(调用超类构造函数)

默认情况下,子类的构造函数会自动调用超类的 无名无参数的默认构造函数。 超类的构造函数在子类构造函数体开始执行的位置调用。 如果提供了一个 initializer list(初始化参数列表) ,则初始化参数列表在超类构造函数执行之前执行。 下面是构造函数执行顺序:

  • initializer list(初始化参数列表)
  • superclass’s no-arg constructor(超类的无名构造函数)
  • main class’s no-arg constructor(主类的无名构造函数)

如果超类没有无名无参数构造函数, 则你需要手工的调用超类的其他构造函数。 在构造函数参数后使用冒号 (:) 可以调用 超类构造函数。

下面的示例中,Employee 类的构造函数调用 了超类 Person 的命名构造函数。 点击运行按钮( red-run.png ) 来执行代码。

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(findDefaultData());
}

注意: 如果在构造函数的初始化列表中使用 super(),需要把它放到最后。 详情参考 Dart 最佳实践。

警告: 调用超类构造函数的参数无法访问 this。 例如,参数可以为静态函数但是不能是实例函数。

Initializer list(初始化列表)

在构造函数体执行之前除了可以调用超类构造函数之外,还可以 初始化实例参数。 使用逗号分隔初始化表达式。

class Point {
  num x;
  num y;

  Point(this.x, this.y);

  // Initializer list sets instance variables before
  // the constructor body runs.
  Point.fromJson(Map jsonMap)
      : x = jsonMap['x'],
        y = jsonMap['y'] {
    print('In Point.fromJson(): ($x, $y)');
  }
}

警告: 初始化表达式等号右边的部分不能访问 this。

初始化列表非常适合用来设置 final 变量的值。 下面示例代码中初始化列表设置了三个 final 变量的值。 点击运行按钮 ( red-run.png ) 执行代码:

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

Redirecting constructors(重定向构造函数)

有时候一个构造函数会调动类中的其他构造函数。 一个重定向构造函数是没有代码的,在构造函数声明后,使用 冒号调用其他构造函数。

class Point {
  num x;
  num y;

  // The main constructor for this class.
  Point(this.x, this.y);

  // Delegates to the main constructor.
  Point.alongXAxis(num x) : this(x, 0);
}

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

Factory constructors(工厂方法构造函数)

如果一个构造函数并不总是返回一个新的对象,则使用 factory 来定义 这个构造函数。例如,一个工厂构造函数 可能从缓存中获取一个实例并返回,或者 返回一个子类型的实例。

下面代码演示工厂构造函数 如何从缓存中返回对象。

class Logger {
  final String name;
  bool mute = false;

  // _cache is library-private, thanks to the _ in front
  // of its name.
  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);
    }
  }
}

注意: 工厂构造函数无法访问 this。

使用 new 关键字来调用工厂构造函数。

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

Methods(函数)

函数是类中定义的方法,是类对象的行为。

Instance methods(实例函数) 对象的实例函数可以访问 this。 例如下面示例中的 distanceTo() 函数 就是实例函数:

import 'dart:math';

class Point {
  num x;
  num 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);
  }
}

Getters and setters

Getters 和 setters 是用来设置和访问对象属性的特殊 函数。每个实例变量都隐含的具有一个 getter, 如果变量不是 final 的则还有一个 setter。 你可以通过实行 getter 和 setter 来创建新的属性, 使用 get 和 set 关键字定义 getter 和 setter:

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

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

  // Define two calculated properties: right and 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;
}

main() {
  var rect = new Rectangle(3, 4, 20, 15);
  assert(rect.left == 3);
  rect.right = 12;
  assert(rect.left == -8);
}

getter 和 setter 的好处是,你可以开始使用实例变量,后来 你可以把实例变量用函数包裹起来,而调用你代码的地方不需要修改。

注意: 像 (++) 这种操作符不管是否定义 getter 都会正确的执行。 为了避免其他副作用, 操作符只调用 getter 一次,然后 把其值保存到一个临时变量中。

Abstract methods(抽象函数)

实例函数、 getter、和 setter 函数可以为抽象函数, 抽象函数是只定义函数接口但是没有实现的函数,由子类来 实现该函数。如果用分号来替代函数体则这个函数就是抽象函数。

abstract class Doer {
  // ...Define instance variables and methods...

  void doSomething(); // Define an abstract method.
}

class EffectiveDoer extends Doer {
  void doSomething() {
    // ...Provide an implementation, so the method is not abstract here...
  }
}

调用一个没实现的抽象函数会导致运行时异常。

另外参考 抽象类。

Overridable operators(可覆写的操作符)

下表中的操作符可以被覆写。 例如,如果你定义了一个 Vector 类, 你可以定义一个 + 函数来实现两个向量相加。

< + | [] > / ^ []= <= ~/ & ~ >= * << == – % >> 下面是覆写了 + 和 - 操作符的示例:

class Vector {
  final int x;
  final int y;
  const Vector(this.x, this.y);

  /// Overrides + (a + b).
  Vector operator +(Vector v) {
    return new Vector(x + v.x, y + v.y);
  }

  /// Overrides - (a - b).
  Vector operator -(Vector v) {
    return new Vector(x - v.x, y - v.y);
  }
}

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

  // v == (2, 3)
  assert(v.x == 2 && v.y == 3);

  // v + w == (4, 5)
  assert((v + w).x == 4 && (v + w).y == 5);

  // v - w == (0, 1)
  assert((v - w).x == 0 && (v - w).y == 1);
}

如果你覆写了 == ,则还应该覆写对象的 hashCode getter 函数。 关于 覆写 == 和 hashCode 的示例请参考 实现 map 的 keys。

关于覆写的更多信息请参考 扩展类。

Abstract classes(抽象类)

使用 abstract 修饰符定义一个 抽象类—一个不能被实例化的类。 抽象类通常用来定义接口, 以及部分实现。如果你希望你的抽象类 是可示例化的,则定义一个 工厂 构造函数。

抽象类通常具有 抽象函数。 下面是定义具有抽象函数的 抽象类的示例:

// This class is declared abstract and thus
// can't be instantiated.
abstract class AbstractContainer {
  // ...Define constructors, fields, methods...

  void updateChildren(); // Abstract method.
}

下面的类不是抽象的,但是定义了一个抽象函数,这样 的类是可以被实例化的:

class SpecializedContainer extends AbstractContainer {
  // ...Define more constructors, fields, methods...

  void updateChildren() {
    // ...Implement updateChildren()...
  }

  // Abstract method causes a warning but
  // doesn't prevent instantiation.
  void doSomething();
}

Implicit interfaces(隐式接口)

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

一个类可以通过 implements 关键字来实现一个或者多个接口, 并实现每个接口定义的 API。 例如:

// A person. The implicit interface contains greet().
class Person {
  // In the interface, but visible only in this library.
  final _name;

  // Not in the interface, since this is a constructor.
  Person(this._name);

  // In the interface.
  String greet(who) => 'Hello, $who. I am $_name.';
}

// An implementation of the Person interface.
class Imposter implements Person {
  // We have to define this, but we don't use it.
  final _name = "";

  String greet(who) => 'Hi $who. Do you know who I am?';
}

greetBob(Person person) => person.greet('bob');

main() {
  print(greetBob(new Person('kathy')));
  print(greetBob(new Imposter()));
}

下面是实现多个接口 的示例:

class Point implements Comparable, Location {
  // ...
}
Extending a class(扩展类)
使用 extends 定义子类, supper 引用 超类:

class Television {
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }
  // ...
}

class SmartTelevision extends Television {
  void turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    _initializeMemory();
    _upgradeApps();
  }
  // ...
}

子类可以覆写实例函数,getter 和 setter。 下面是覆写 Object 类的 noSuchMethod() 函数的例子, 如果调用了对象上不存在的函数,则就会触发 noSuchMethod() 函 数。

class A {
  // Unless you override noSuchMethod, using a
  // non-existent member results in a NoSuchMethodError.
  void noSuchMethod(Invocation mirror) {
    print('You tried to use a non-existent member:' +
          '${mirror.memberName}');
  }
}

还可以使用 @override 注解来 表明你的函数是想覆写超类的一个函数:

class A {
  @override
  void noSuchMethod(Invocation mirror) {
    // ...
  }
}

如果你使用 noSuchMethod() 函数来实现每个可能的 getter 、setter、 以及其他类型的函数,你可以使用 @proxy 注解来避免警告信息:

@proxy
class A {
  void noSuchMethod(Invocation mirror) {
    // ...
  }
}

如果你知道编译时的具体类型,则可以 实现这些类来避免警告,和 使用 @proxy 效果一样:

class A implements SomeClass, SomeOtherClass {
  void noSuchMethod(Invocation mirror) {
    // ...
  }
}

关于注解的详情,请参考 Metadata。

Enumerated types(枚举类型)

枚举类型通常称之为 enumerations 或者 enums, 是一种特殊的类,用来表现一个固定 数目的常量。

Using enums 使用 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<Color> colors = Color.values;
assert(colors[2] == Color.blue);

可以在 switch 语句 中使用枚举。 如果在 switch (e) 中的 e 的类型为枚举类, 如果你没有处理所有该枚举类型的值的话,则会抛出一个警告:

enum Color {
  red,
  green,
  blue
}
// ...
Color aColor = Color.blue;
switch (aColor) {
  case Color.red:
    print('Red as roses!');
    break;
  case Color.green:
    print('Green as grass!');
    break;
  default: // Without this, you see a WARNING.
    print(aColor);  // 'Color.blue'
}

枚举类型具有如下的限制:

  • 无法继承枚举类型、无法使用 mix in、无法实现一个枚举类型
  • 无法显示的初始化一个枚举类型

详情请参考 Dart 语言规范。

Adding features to a class: mixins(为类添加新的功能)

Mixins 是一种在多类继承中重用 一个类代码的方法。

使用 with 关键字后面为一个或者多个 mixin 名字来使用 mixin。 下面是示例显示了如何使用 mixin:

class Musician extends Performer with Musical {
  // ...
}

class Maestro extends Person
    with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}

定义一个类继承 Object,该类没有构造函数, 不能调用 super ,则该类就是一个 mixin。例如:

abstract class 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');
    }
  }
}

注意: 从 Dart 1.13 开始, 这两个限制在 Dart VM 上 没有那么严格了: Mixins 可以继承其他类,不再限制为继承 Object Mixins 可以调用 super()。 这种 “super mixins” 还 无法在 dart2js 中使用 并且需要在 dartanalyzer 中使用 –supermixin 参数。

详情,请参考 Mixins in Dart。

Class variables and methods(类变量和函数)

使用 static 关键字来实现类级别的变量和函数。

Static variables(静态变量)

静态变量对于类级别的状态是 非常有用的:

class Color {
  static const red =
      const Color('red'); // A constant static variable.
  final String name;      // An instance variable.
  const Color(this.name); // A constant constructor.
}

main() {
  assert(Color.red.name == 'red');
}

静态变量在第一次使用的时候才被初始化。

注意: 这里准守代码风格推荐 命名规则,使用 lowerCamelCase 来命名常量。

Static methods(静态函数)

静态函数不再类实例上执行, 所以无法访问 this。例如:

import 'dart:math';

class Point {
  num x;
  num 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);
  }
}

main() {
  var a = new Point(2, 2);
  var b = new Point(4, 4);
  var distance = Point.distanceBetween(a, b);
  assert(distance < 2.9 && distance > 2.8);
}

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

静态函数还可以当做编译时常量使用。例如, 你可以把静态函数当做常量构造函数的参数来使用。