Scala 继承:单继承机制与灵活的重写特性
继承是面向对象编程的核心特性之一,用于实现代码复用和多态。Scala 支持单继承(一个类只能有一个直接父类),通过 extends 关键字实现,同时提供了严格的方法和字段重写规则,确保代码的可维护性。本文将详细解析 Scala 继承的语法、重写机制及与 Java 的差异。
继承的基本语法
Scala 继承的基本语法与 Java 相似,使用 extends 关键字指定父类,子类将继承父类的非私有成员(属性和方法)。
语法格式
1 | class 子类名 extends 父类名 { |
示例:简单继承
1 | // 父类:Animal |
核心规则
- 单继承:一个类只能直接继承一个父类(与 Java 相同)。
- 构造器调用:子类构造器必须直接或间接调用父类构造器(仅主构造器可调用父类构造器)。
- 访问权限:子类可继承父类的
public、protected成员,不可继承private成员。
父类构造器的调用
Scala 中,子类构造器必须在初始化时调用父类的构造器,且只有主构造器可以直接调用父类构造器,辅助构造器需通过调用主构造器间接完成。
示例:带参数的父类构造器
1 | // 父类:Person(带参数的主构造器) |
关键点
- 子类主构造器通过
extends 父类名(参数)调用父类构造器。 - 辅助构造器需通过
this(...)调用子类主构造器,从而间接调用父类构造器(与 Java 中super(...)需在第一行类似)。
方法重写(Override)
子类可重写父类的方法以实现特定逻辑,Scala 对方法重写有严格限制:必须使用 override 修饰符,且重写方法的参数列表和返回值类型必须与父类一致。
基本重写示例
1 | class Animal { |
调用父类方法(super)
通过 super 关键字在子类中调用父类的被重写方法:
1 | class Cat extends Animal { |
禁止重写(final)
用 final 修饰父类方法,可阻止子类重写:
1 | class Bird { |
字段重写(Scala 特有)
与 Java 不同,Scala 允许子类重写父类的字段(属性),但需遵循严格规则:
- 父类字段必须是
val(不可变),子类重写时也必须用val。 - 若父类字段是
var(可变),子类只能重写抽象的var字段(未初始化的字段,且父类必须是抽象类)。
重写 val 字段
1 | class Parent { |
底层原理
Scala 中 val 字段会生成 getter 方法(如 age()),字段重写本质是重写 getter 方法,因此多态访问时会返回子类的值。
重写抽象 var 字段
父类为抽象类且字段未初始化(抽象字段)时,子类可用 var 重写:
1 | // 抽象父类(无方法体或字段未初始化) |
规则
- 抽象字段无需初始化,子类必须初始化。
- 重写抽象
var字段时,override修饰符可省略(编译器自动推断)。
禁止字段重写
- 父类字段用
final val修饰,子类不可重写。 - 父类用
final修饰(final class Parent),则整个类不可被继承。
继承与多态
Scala 继承支持多态性:父类引用可指向子类对象,调用方法时会执行子类的重写实现。
1 | // 父类 |
与 Java 继承的核心差异
| 特性 | Scala | Java |
|---|---|---|
| 字段重写 | 支持重写 val 字段(本质重写 getter) |
不支持字段重写,仅支持字段隐藏 |
override 修饰符 |
重写非抽象成员必须加 override |
重写方法需加 @Override(注解,非强制) |
| 抽象字段 | 未初始化的字段即为抽象字段,无需 abstract |
无抽象字段概念,需通过抽象方法模拟 |
| 主构造器调用父类 | 子类主构造器直接在 extends 后调用 |
构造器中通过 super(...) 调用 |
protected 权限 |
仅子类可见(同包不可见) | 子类和同包类均可见 |
最佳实践
- 优先组合而非继承:继承可能导致类耦合度高,复杂场景下建议用 “组合”(将对象作为属性)替代。
- 明确使用
override:重写方法或字段时必须加override,增强代码可读性。 - 限制可变字段的重写:
var字段重写仅用于抽象场景,非抽象var字段禁止重写(编译器会报错)。 - 合理使用
final:对不希望被继承的类或重写的方法 / 字段,用final修饰,避免滥用继承。