0%

scala继承

Scala 继承:单继承机制与灵活的重写特性

继承是面向对象编程的核心特性之一,用于实现代码复用和多态。Scala 支持单继承(一个类只能有一个直接父类),通过 extends 关键字实现,同时提供了严格的方法和字段重写规则,确保代码的可维护性。本文将详细解析 Scala 继承的语法、重写机制及与 Java 的差异。

继承的基本语法

Scala 继承的基本语法与 Java 相似,使用 extends 关键字指定父类,子类将继承父类的非私有成员(属性和方法)。

语法格式

1
2
3
class 子类名 extends 父类名 {
// 子类体(可定义新成员或重写父类成员)
}

示例:简单继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 父类:Animal
class Animal {
var name: String = _ // 动物名称(默认权限)

def sayHello(): Unit = {
println(s"我是${name}")
}
}

// 子类:Tiger(继承自Animal)
class Tiger extends Animal {
// 子类新增方法
def run(): Unit = {
println(s"${name}在奔跑")
}
}

// 测试
object TestInheritance {
def main(args: Array[String]): Unit = {
val tiger = new Tiger()
tiger.name = "东北虎" // 继承父类的name属性
tiger.sayHello() // 继承父类的sayHello方法 → 输出:我是东北虎
tiger.run() // 调用子类新增方法 → 输出:东北虎在奔跑
}
}

核心规则

  1. 单继承:一个类只能直接继承一个父类(与 Java 相同)。
  2. 构造器调用:子类构造器必须直接或间接调用父类构造器(仅主构造器可调用父类构造器)。
  3. 访问权限:子类可继承父类的 publicprotected 成员,不可继承 private 成员。

父类构造器的调用

Scala 中,子类构造器必须在初始化时调用父类的构造器,且只有主构造器可以直接调用父类构造器,辅助构造器需通过调用主构造器间接完成。

示例:带参数的父类构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 父类:Person(带参数的主构造器)
class Person(var name: String, var age: Int) {
println(s"Person构造器:$name, $age")
}

// 子类:Student(继承自Person)
class Student(name: String, age: Int, var studentId: String)
extends Person(name, age) { // 主构造器调用父类构造器
println(s"Student构造器:$name, $age, $studentId")

// 辅助构造器(必须调用主构造器,间接调用父类构造器)
def this(name: String) {
this(name, 18, "默认ID") // 调用子类主构造器
}
}

// 测试
val s1 = new Student("张三", 20, "2023001")
// 输出:
// Person构造器:张三, 20
// Student构造器:张三, 20, 2023001

val s2 = new Student("李四")
// 输出:
// Person构造器:李四, 18
// Student构造器:李四, 18, 默认ID

关键点

  • 子类主构造器通过 extends 父类名(参数) 调用父类构造器。
  • 辅助构造器需通过 this(...) 调用子类主构造器,从而间接调用父类构造器(与 Java 中 super(...) 需在第一行类似)。

方法重写(Override)

子类可重写父类的方法以实现特定逻辑,Scala 对方法重写有严格限制:必须使用 override 修饰符,且重写方法的参数列表和返回值类型必须与父类一致。

基本重写示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Animal {
def whoAmI(): Unit = {
println("我是动物")
}
}

class Dog extends Animal {
// 重写父类方法,必须加override
override def whoAmI(): Unit = {
println("我是狗")
}
}

// 测试
val dog = new Dog()
dog.whoAmI() // 输出:我是狗

调用父类方法(super)

通过 super 关键字在子类中调用父类的被重写方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Cat extends Animal {
override def whoAmI(): Unit = {
super.whoAmI() // 调用父类方法
println("我是猫")
}
}

// 测试
val cat = new Cat()
cat.whoAmI()
// 输出:
// 我是动物
// 我是猫

禁止重写(final)

final 修饰父类方法,可阻止子类重写:

1
2
3
4
5
6
7
8
9
class Bird {
final def fly(): Unit = { // final方法,不可重写
println("鸟在飞")
}
}

class Ostrich extends Bird {
// override def fly(): Unit = { ... } // 编译错误:无法重写final方法
}

字段重写(Scala 特有)

与 Java 不同,Scala 允许子类重写父类的字段(属性),但需遵循严格规则:

  • 父类字段必须是 val(不可变),子类重写时也必须用 val
  • 若父类字段是 var(可变),子类只能重写抽象的 var 字段(未初始化的字段,且父类必须是抽象类)。

重写 val 字段

1
2
3
4
5
6
7
8
9
10
11
class Parent {
val age: Int = 40 // 父类val字段
}

class Child extends Parent {
override val age: Int = 10 // 子类重写val字段,必须加override
}

// 测试
val parent: Parent = new Child() // 多态:父类引用指向子类对象
println(parent.age) // 输出:10(调用子类重写的字段)
底层原理

Scala 中 val 字段会生成 getter 方法(如 age()),字段重写本质是重写 getter 方法,因此多态访问时会返回子类的值。

重写抽象 var 字段

父类为抽象类且字段未初始化(抽象字段)时,子类可用 var 重写:

1
2
3
4
5
6
7
8
9
10
11
12
// 抽象父类(无方法体或字段未初始化)
abstract class AbstractParent {
var name: String // 抽象var字段(未初始化)
}

class ConcreteChild extends AbstractParent {
override var name: String = " concrete child" // 重写抽象var字段,override可省略
}

// 测试
val child = new ConcreteChild()
println(child.name) // 输出:concrete child
规则
  • 抽象字段无需初始化,子类必须初始化。
  • 重写抽象 var 字段时,override 修饰符可省略(编译器自动推断)。

禁止字段重写

  • 父类字段用 final val 修饰,子类不可重写。
  • 父类用 final 修饰(final class Parent),则整个类不可被继承。

继承与多态

Scala 继承支持多态性:父类引用可指向子类对象,调用方法时会执行子类的重写实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 父类
class Shape {
def area(): Double = 0.0 // 计算面积的方法
}

// 子类:圆形
class Circle(radius: Double) extends Shape {
override def area(): Double = Math.PI * radius * radius
}

// 子类:矩形
class Rectangle(width: Double, height: Double) extends Shape {
override def area(): Double = width * height
}

// 测试多态
object ShapeDemo {
def main(args: Array[String]): Unit = {
val shapes: Array[Shape] = Array(
new Circle(2.0),
new Rectangle(3.0, 4.0)
)

for (shape <- shapes) {
println(s"面积:${shape.area()}") // 自动调用子类重写的方法
}
// 输出:
// 面积:12.566370614359172
// 面积:12.0
}
}

与 Java 继承的核心差异

特性 Scala Java
字段重写 支持重写 val 字段(本质重写 getter) 不支持字段重写,仅支持字段隐藏
override 修饰符 重写非抽象成员必须加 override 重写方法需加 @Override(注解,非强制)
抽象字段 未初始化的字段即为抽象字段,无需 abstract 无抽象字段概念,需通过抽象方法模拟
主构造器调用父类 子类主构造器直接在 extends 后调用 构造器中通过 super(...) 调用
protected 权限 仅子类可见(同包不可见) 子类和同包类均可见

最佳实践

  1. 优先组合而非继承:继承可能导致类耦合度高,复杂场景下建议用 “组合”(将对象作为属性)替代。
  2. 明确使用 override:重写方法或字段时必须加 override,增强代码可读性。
  3. 限制可变字段的重写var 字段重写仅用于抽象场景,非抽象 var 字段禁止重写(编译器会报错)。
  4. 合理使用 final:对不希望被继承的类或重写的方法 / 字段,用 final 修饰,避免滥用继承。

欢迎关注我的其它发布渠道