继承

本章节重点介绍类继承的基本概念,包括子类的声明单继承方法重写noSuchMethod()等。

声明一个子类

子类继承父类,意味着子类继承了父类的实例成员,包括实例字段和实例方法。使用extend关键字创建一个子类。

// ex511.dart
class Mammal {
  String name; // 1

  Mammal(this.name); // 2

  void play() => print('$name is playing'); // 3

  void sleep() => print('$name is sleeping'); // 3a
}

// 4
class Dolphin extends Mammal {
  Dolphin(super.name); // 5
}

void main() {
  var dolphin = Dolphin('Dolphin'); // 6
  print(dolphin.name); // 7 Output: Dolphin
  dolphin.play(); // 7a Output: Dolphin is playing
  dolphin.sleep(); // 7b Output: Dolphin is sleeping
}
  1. Mammal 类有一个实例变量 name
  2. 这是 Mammal 类的构造函数;
  3. Mammal 类有2个实例方法,playsleep
  4. Dolphin 继承了 Mammal 类;
  5. Dolphin 类的构造函数,使用 super 关键字引用父类;
  6. 实例化 Dolphin 类;
  7. Dolphin 类从 Mammal 类继承了实例成员,包括1个实例变量和2个实例方法。

除了 Null 之外,Dart中的每个类都隐式地继承了 Object 类。此例中的 Mammal 类即:

class Mammal extends Object {
  // ... (omitted for brevity)
}

但这里的 extends Object 完全没必要写出来。

单继承

Dart只允许单继承,不支持多继承,即一个类只能继承一个父类。

// ex512.dart
import 'ex511.dart';

// Error: Each class definition can have at most one extends clause.
// Try choosing one superclass and define your class to implement (or mix in) the others.
// class CuteDolphin extends Dolphin, Mammal  {
//                                  ^
class CuteDolphin extends Dolphin, Mammal  {
  CuteDolphin(super.name);
}

这里 CuteDolphin 试图同时继承 DolphinMammal 类,导致编译失败。 解决方法即让 CuteDolphin 只继承 Dolphin 类。

class CuteDolphin extends Dolphin {
  CuteDolphin(super.name);
}

再谈构造函数

每个类的生成式构造函数被执行时,一定会或显式或隐式地先执行父类的生成式构造函数,即构造函数的第一行代码是调用父构造函数。

// ex513.dart
import 'ex511.dart';

// Error: The implicitly called unnamed constructor from 'Mammal' has required parameters.
// Try adding an explicit super initializer with the required arguments.
//   Ape();
//   ^
class Ape extends Mammal {
  Ape();
}

// Error: The superclass, 'Mammal', has no unnamed constructor that takes no arguments.
// class Elephant extends Mammal {}
//       ^
class Elephant extends Mammal {}

Ape 类的构造函数 Ape(),隐式地调用父类的无参构造函数 Mammal() 引起编译错误,编译器提示Mammal类的unnamed构造函数需要required参数。

Elephant 类默认的构造函数Elephant(),隐式地调用父类的无参构造函数 Mammal() ,编译器提示这样的父构造函数不存在。

方法重写

子类可以重写(override)实例方法(包括运算符)、getter 和 setter。使用 @override 注解来表明重写意图。

// ex514.dart
import 'ex511.dart';

class Badger extends Mammal {
  Badger(super.name);

  @override
  void play() => print('$name Badger is playing');
}

void main() {
  var bader = Badger('B');
  bader.play(); // Output: B Badger is playing
}

重写方法(A)的声明必须与被重写方法(B)在以下几个方面相匹配:

  • A的返回类型必须与B的相同(或为其子类型);
  • A的参数类型必须与B的相同(或为其超类型);
  • 如果B接受 n 个位置参数,则A也必须接受 n 个位置参数;
  • 泛型方法不能重写非泛型方法,反之亦然(泛型乃第6章内容)。
// ex515.dart
import 'ex511.dart';

class Dog extends Mammal {
  Dog(super.name);

  Object sing(int times) => "$name: ${'woof~ ' * times}";
}

class Corgi extends Dog {
  Corgi(super.name);

  @override
  String sing(num times) => "$name: ${'woooof~ ' * times.toInt()}";
}

void main() {
  var corgi = Corgi('Rover');
  print(corgi.sing(3)); // Output: Rover: woooof~ woooof~ woooof~
}

请仔细观察此例中 Corgi.sing(int) 实例方法如何重写了 Dog.sing(int)

NOTE: 如果重写 ==,也应当重写 Object.hashCode getter, 原因与Map的实现有关。

noSuchMethod()

先看一个例子。

// ex516.dart
class Fox {}

void main() {
  dynamic f = Fox();
  f.sing();
  // Unhandled exception:
  // NoSuchMethodError: Class 'Fox' has no instance method 'sing'.
  // Receiver: Instance of 'Fox'
  // Tried calling: sing()
}

此程序调用一个不存在的方法 'f.sing()', 引起运行时异常: NoSuchMethodError: Class 'Fox' has no instance method 'sing',这是Object 类中的 noSuchMethod 方法的默认行为,可以重写它进而实现一些特殊的功能。

// ex517.dart
class Gorilla {
  @override
  dynamic noSuchMethod(Invocation invocation) {
    return """you're invoking ${invocation.memberName}
  method             : ${invocation.isMethod}  
  positionalArguments: ${invocation.positionalArguments}
  namedArguments     : ${invocation.namedArguments}""";
  }
}

void main() {
  dynamic g = Gorilla();
  print(g.sing('a song'));
  // Output:
  // you're invoking Symbol("sing")
  // method             : true  
  // positionalArguments: [a song]
  // namedArguments     : {}
}

参考资料