Dart 继承与多态
继承是面向对象编程的三大特性之一,它允许一个类基于另一个类进行扩展。
本章介绍 Dart 中的 extends 单继承、方法重写、super 关键字和抽象类。
extends 单继承
Dart 只支持单继承——每个类只能有一个直接父类。
但继承链可以很长:子类继承父类,父类再继承祖父类,层层传递。
实例
// 父类(基类、超类)
class Animal {
String name;
Animal(this.name);
void eat() {
print('$name 在吃东西');
}
void sleep() {
print('$name 在睡觉');
}
}
// 子类:使用 extends 继承 Animal
class Dog extends Animal {
String breed; // 品种
// 子类构造函数需要调用父类构造函数
Dog(String name, this.breed) : super(name);
// 子类新增的方法
void bark() {
print('$name($breed)在汪汪叫!');
}
}
void main() {
var dog = Dog('旺财', '金毛');
// 调用从父类继承的方法
dog.eat();
dog.sleep();
// 调用子类自己的方法
dog.bark();
// 子类对象也是父类类型
Animal animal = dog; // Dog 是 Animal,可以向上转型
animal.eat();
}
class Animal {
String name;
Animal(this.name);
void eat() {
print('$name 在吃东西');
}
void sleep() {
print('$name 在睡觉');
}
}
// 子类:使用 extends 继承 Animal
class Dog extends Animal {
String breed; // 品种
// 子类构造函数需要调用父类构造函数
Dog(String name, this.breed) : super(name);
// 子类新增的方法
void bark() {
print('$name($breed)在汪汪叫!');
}
}
void main() {
var dog = Dog('旺财', '金毛');
// 调用从父类继承的方法
dog.eat();
dog.sleep();
// 调用子类自己的方法
dog.bark();
// 子类对象也是父类类型
Animal animal = dog; // Dog 是 Animal,可以向上转型
animal.eat();
}
旺财 在吃东西 旺财 在睡觉 旺财(金毛)在汪汪叫! 旺财 在吃东西
子类可以访问父类中所有非私有的成员变量和方法。
私有成员(以下划线开头)不会被继承。
Dart 中没有 public、protected、private 关键字。以下划线(_)开头的成员是库级私有(library-private),只能在同一文件中访问。这与 Java/C++ 的访问控制不同。
方法重写 @override
子类可以重写(override)父类的方法,提供自己的实现。
使用 @override 注解明确表示重写意图,编译器会帮你检查是否正确重写。
实例
class Animal {
String name;
Animal(this.name);
// 父类的方法
void makeSound() {
print('$name 发出了一些声音');
}
@override
String toString() {
return '动物: $name';
}
}
class Cat extends Animal {
Cat(String name) : super(name);
// @override 注解:告诉编译器我是在重写父类方法
@override
void makeSound() {
print('$name 喵喵叫~');
}
}
class Duck extends Animal {
Duck(String name) : super(name);
@override
void makeSound() {
print('$name 嘎嘎叫!');
}
}
void main() {
var cat = Cat('小花');
var duck = Duck('唐老鸭');
cat.makeSound();
duck.makeSound();
// 多态:同一个方法,不同对象有不同表现
List<Animal> animals = [
Cat('咪咪'),
Duck('鸭鸭'),
Cat('球球'),
];
print('--- RUNOOB 动物园 ---');
for (var animal in animals) {
animal.makeSound(); // 运行时会调用实际类型的方法
}
}
String name;
Animal(this.name);
// 父类的方法
void makeSound() {
print('$name 发出了一些声音');
}
@override
String toString() {
return '动物: $name';
}
}
class Cat extends Animal {
Cat(String name) : super(name);
// @override 注解:告诉编译器我是在重写父类方法
@override
void makeSound() {
print('$name 喵喵叫~');
}
}
class Duck extends Animal {
Duck(String name) : super(name);
@override
void makeSound() {
print('$name 嘎嘎叫!');
}
}
void main() {
var cat = Cat('小花');
var duck = Duck('唐老鸭');
cat.makeSound();
duck.makeSound();
// 多态:同一个方法,不同对象有不同表现
List<Animal> animals = [
Cat('咪咪'),
Duck('鸭鸭'),
Cat('球球'),
];
print('--- RUNOOB 动物园 ---');
for (var animal in animals) {
animal.makeSound(); // 运行时会调用实际类型的方法
}
}
小花 喵喵叫~ 唐老鸭 嘎嘎叫! --- RUNOOB 动物园 --- 咪咪 喵喵叫~ 鸭鸭 嘎嘎叫! 球球 喵喵叫~
上面这个例子展示了多态(Polymorphism):同一个方法调用 animal.makeSound(),实际执行的行为取决于对象的真实类型。
使用 @override 注解有两个好处:一是让代码意图更清晰,二是如果父类没有同名方法(比如拼写错误),编译器会报错提醒。建议始终加 @override。
super 关键字
super 代表父类对象,子类通过 super 来调用父类的构造函数、方法和属性。
实例
class Vehicle {
String brand;
int year;
Vehicle(this.brand, this.year);
void start() {
print('$brand 车辆启动了');
}
void displayInfo() {
print('品牌: $brand, 年份: $year');
}
}
class ElectricCar extends Vehicle {
int batteryLevel;
ElectricCar(String brand, int year, this.batteryLevel)
: super(brand, year); // 调用父类构造函数
@override
void start() {
// 在子类方法中调用父类的方法
super.start(); // 先执行父类的启动逻辑
print('电池电量: $batteryLevel%');
print('电动机无声启动完成');
}
@override
void displayInfo() {
super.displayInfo(); // 先显示父类的基础信息
print('电池电量: $batteryLevel%'); // 再补充子类的特有信息
}
}
void main() {
var car = ElectricCar('RUNOOB EV', 2026, 85);
car.start();
print('---');
car.displayInfo();
}
String brand;
int year;
Vehicle(this.brand, this.year);
void start() {
print('$brand 车辆启动了');
}
void displayInfo() {
print('品牌: $brand, 年份: $year');
}
}
class ElectricCar extends Vehicle {
int batteryLevel;
ElectricCar(String brand, int year, this.batteryLevel)
: super(brand, year); // 调用父类构造函数
@override
void start() {
// 在子类方法中调用父类的方法
super.start(); // 先执行父类的启动逻辑
print('电池电量: $batteryLevel%');
print('电动机无声启动完成');
}
@override
void displayInfo() {
super.displayInfo(); // 先显示父类的基础信息
print('电池电量: $batteryLevel%'); // 再补充子类的特有信息
}
}
void main() {
var car = ElectricCar('RUNOOB EV', 2026, 85);
car.start();
print('---');
car.displayInfo();
}
RUNOOB EV 车辆启动了 电池电量: 85% 电动机无声启动完成 --- 品牌: RUNOOB EV, 年份: 2026 电池电量: 85%
super 的使用场景总结
| 场景 | 语法 | 说明 |
|---|---|---|
| 调用父类构造函数 | : super(args) | 必须在初始化列表中调用 |
| 调用父类方法 | super.methodName() | 可在子类重写方法中调用 |
| 访问父类属性 | super.propertyName | 访问父类的成员变量 |
抽象类 abstract
抽象类是不能被直接实例化的类,它只定义接口规范,由子类实现具体逻辑。
实例
// abstract 关键字声明抽象类
abstract class Shape {
// 抽象方法:没有方法体,子类必须实现
double getArea();
double getPerimeter();
// 抽象类中也可以有具体方法
void describe() {
print('这是一个形状,面积: ${getArea()}, 周长: ${getPerimeter()}');
}
}
class Rectangle extends Shape {
double width;
double height;
Rectangle(this.width, this.height);
@override
double getArea() => width * height;
@override
double getPerimeter() => 2 * (width + height);
}
class Circle extends Shape {
double radius;
Circle(this.radius);
@override
double getArea() => 3.14159 * radius * radius;
@override
double getPerimeter() => 2 * 3.14159 * radius;
}
void main() {
// Shape shape = Shape(); // 错误!抽象类不能实例化
var rect = Rectangle(10, 5);
var circle = Circle(7);
rect.describe();
circle.describe();
// 抽象类作为类型使用
List<Shape> shapes = [
Rectangle(3, 4),
Circle(5),
Rectangle(6, 2),
];
print('--- RUNOOB 形状统计 ---');
double totalArea = 0;
for (var shape in shapes) {
totalArea += shape.getArea();
}
print('总面积: ${totalArea.toStringAsFixed(2)}');
}
abstract class Shape {
// 抽象方法:没有方法体,子类必须实现
double getArea();
double getPerimeter();
// 抽象类中也可以有具体方法
void describe() {
print('这是一个形状,面积: ${getArea()}, 周长: ${getPerimeter()}');
}
}
class Rectangle extends Shape {
double width;
double height;
Rectangle(this.width, this.height);
@override
double getArea() => width * height;
@override
double getPerimeter() => 2 * (width + height);
}
class Circle extends Shape {
double radius;
Circle(this.radius);
@override
double getArea() => 3.14159 * radius * radius;
@override
double getPerimeter() => 2 * 3.14159 * radius;
}
void main() {
// Shape shape = Shape(); // 错误!抽象类不能实例化
var rect = Rectangle(10, 5);
var circle = Circle(7);
rect.describe();
circle.describe();
// 抽象类作为类型使用
List<Shape> shapes = [
Rectangle(3, 4),
Circle(5),
Rectangle(6, 2),
];
print('--- RUNOOB 形状统计 ---');
double totalArea = 0;
for (var shape in shapes) {
totalArea += shape.getArea();
}
print('总面积: ${totalArea.toStringAsFixed(2)}');
}
这是一个形状,面积: 50.0, 周长: 30.0 这是一个形状,面积: 153.93791, 周长: 43.98226 --- RUNOOB 形状统计 --- 总面积: 108.54
抽象类的核心价值是"定义契约"。它告诉所有子类:你必须实现这些方法,否则编译不通过。这让团队协作更安全——实现者不会遗漏方法,调用者确信方法一定存在。
