类的继承

296 阅读4分钟

1. 类的继承的定义

Scala 中的类继承是「面向对象编程」的核心特性之一,核心定义:

  • 让一个类(子类 / 派生类,Subclass)复用另一个类(父类 / 基类,Superclass)的属性和方法,并可以在此基础上扩展新的功能或重写原有功能;
  • 目的:代码复用(避免重复编写父类已有逻辑)、功能扩展(子类在父类基础上新增特性);
  • Scala 限制:单继承(一个子类只能直接继承一个父类),但可通过「特质(Trait)」实现类似多继承的效果(后续补充)。

关键概念

  • 父类:被继承的类(如 Animal);
  • 子类:继承父类的类(如 DogCat);
  • 继承关系:子类 is-a 父类(Dog is-a Animal),体现「泛化」关系;
  • 重写(Override):子类重新实现父类的方法 / 属性,需显式用 override 关键字(Scala 强制要求,避免误重写)。

2. 继承的语法

1. 基础继承语法(子类继承父类)

// 父类定义(可含属性、方法)
class 父类名(参数列表) {
  // 父类属性、方法
}

// 子类继承父类:extends 关键字
class 子类名(参数列表) extends 父类名(父类构造参数) {
  // 子类新增属性、方法
  // 重写父类方法/属性(需加 override)
}

2. 关键语法规则

  • 继承关键字:extends(唯一继承关键字,无 implements/extends 区分,特质也用 extends);
  • 父类构造:子类构造时必须先调用父类构造(通过 extends 父类名(参数) 显式传递父类构造参数);
  • 重写关键字:override(重写父类非抽象方法 / 属性时必须加,重写抽象方法可省略,但推荐加);
  • 访问权限:父类的 private 成员(属性 / 方法)子类不可访问,protected 成员子类可访问,public 成员(默认)所有类可访问;
  • 禁止继承:父类用 final 修饰,则子类无法继承(如 final class Animal);
  • 禁止重写:父类方法 / 属性用 final 修饰,则子类无法重写(如 final def eat(): Unit)。

3. 抽象类继承(父类为抽象类)

若父类包含未实现的抽象方法 / 属性(无方法体 / 无初始值),则父类需用 abstract 修饰,子类必须实现所有抽象成员(或子类也声明为抽象类):

// 抽象父类(abstract 修饰)
abstract class 抽象父类名 {
  // 抽象属性(无初始值)
  val 抽象属性名: 类型
  // 抽象方法(无方法体)
  def 抽象方法名(参数列表): 返回值类型
}

// 子类实现抽象类
class 子类名 extends 抽象父类名 {
  // 实现抽象属性(需加 override,可省略但推荐)
  override val 抽象属性名: 类型 = 初始值
  // 实现抽象方法(需加 override,可省略但推荐)
  override def 抽象方法名(参数列表): 返回值类型 = {
    // 方法体
  }
}

3. 继承的代码示例

示例 1:基础类继承(非抽象父类)

场景:定义动物父类 Animal,子类 DogCat 继承 Animal,复用 name 属性和 eat() 方法,重写 sound() 方法,新增子类特有方法。

object InheritanceBasicDemo extends App {
  // 父类:动物类(非抽象,含具体属性和方法)
  class Animal(val name: String, val age: Int) {
    // 父类具体方法(可被重写)
    def eat(): Unit = {
      println(s"$name 正在吃食物")
    }

    // 父类具体方法(子类可重写)
    def sound(): Unit = {
      println(s"$name 发出声音")
    }

    // final 方法:子类不可重写
    final def sleep(): Unit = {
      println(s"$name 正在睡觉")
    }
  }

  // 子类:狗类(继承 Animal)
  class Dog(name: String, age: Int, val breed: String) extends Animal(name, age) {
    // 重写父类 sound() 方法(必须加 override)
    override def sound(): Unit = {
      println(s"小狗 $name(品种:$breed)汪汪叫")
    }

    // 子类特有方法(父类无)
    def fetch(): Unit = {
      println(s"小狗 $name 正在叼飞盘")
    }
  }

  // 子类:猫类(继承 Animal)
  class Cat(name: String, age: Int, val color: String) extends Animal(name, age) {
    // 重写父类 sound() 方法
    override def sound(): Unit = {
      println(s"小猫 $name(颜色:$color)喵喵叫")
    }

    // 子类特有方法
    def climbTree(): Unit = {
      println(s"小猫 $name 正在爬树")
    }
  }

  // 测试:创建子类实例
  val dog = new Dog("旺财", 3, "金毛")
  val cat = new Cat("咪咪", 2, "橘色")

  // 调用父类继承的方法
  dog.eat() // 输出:旺财 正在吃食物
  cat.sleep() // 输出:咪咪 正在睡觉

  // 调用重写的方法
  dog.sound() // 输出:小狗 旺财(品种:金毛)汪汪叫
  cat.sound() // 输出:小猫 咪咪(颜色:橘色)喵喵叫

  // 调用子类特有方法
  dog.fetch() // 输出:小狗 旺财 正在叼飞盘
  cat.climbTree() // 输出:小猫 咪咪 正在爬树

  // 访问继承的属性
  println(s"狗的名字:${dog.name},年龄:${dog.age}") // 输出:狗的名字:旺财,年龄:3
}

示例 2:抽象类继承(父类为抽象类)

场景:定义抽象父类 Shape(形状),包含抽象属性 name(形状名称)和抽象方法 area()(计算面积),子类 Circle(圆形)、Rectangle(矩形)实现抽象成员。

object AbstractInheritanceDemo extends App {
  // 抽象父类:形状(abstract 修饰)
  abstract class Shape {
    // 抽象属性:形状名称(无初始值)
    val name: String
    // 抽象方法:计算面积(无方法体)
    def area(): Double
  }

  // 子类:圆形(实现 Shape)
  class Circle(val radius: Double) extends Shape {
    // 实现抽象属性 name(override 可选,推荐加)
    override val name: String = "圆形"

    // 实现抽象方法 area(override 可选,推荐加)
    override def area(): Double = {
      Math.PI * radius * radius // 圆面积公式:πr²
    }
  }

  // 子类:矩形(实现 Shape)
  class Rectangle(val width: Double, val height: Double) extends Shape {
    override val name: String = "矩形"

    override def area(): Double = {
      width * height // 矩形面积公式:长×宽
    }
  }

  // 测试:创建子类实例
  val circle = new Circle(5.0)
  val rectangle = new Rectangle(4.0, 6.0)

  println(s"${circle.name} 的面积:${circle.area().formatted("%.2f")}") // 输出:圆形 的面积:78.54
  println(s"${rectangle.name} 的面积:${rectangle.area()}") // 输出:矩形 的面积:24.0
}

示例 3:构造函数传递与访问权限

场景:演示父类构造参数传递、protected 成员访问、final 类禁止继承。

object InheritanceAdvancedDemo extends App {
  // 父类:Person(含 protected 成员)
  class Person(
    val name: String, // public 属性(默认)
    protected val age: Int // protected 属性:子类可访问,外部不可访问
  ) {
    // protected 方法:子类可访问
    protected def introduce(): String = {
      s"我叫 $name,年龄 $age"
    }
  }

  // 子类:Student(继承 Person)
  class Student(name: String, age: Int, val studentId: String) extends Person(name, age) {
    // 子类访问父类 protected 成员(age 和 introduce())
    def studentIntroduce(): String = {
      s"${introduce()},学号 $studentId"
    }
  }

  // 测试:Student 实例
  val student = new Student("张三", 20, "2025001")
  println(student.name) // 输出:张三(public 可访问)
  // println(student.age) // 编译报错:age 是 protected,外部不可访问
  println(student.studentIntroduce()) // 输出:我叫 张三,年龄 20,学号 2025001

  // final 类:禁止继承
  final class FinalClass {
    def sayHi(): Unit = println("我是 final 类,不可被继承")
  }

  // class SubFinalClass extends FinalClass {} // 编译报错:FinalClass 是 final,无法继承
}

4. 继承与多态

. 多态的定义

多态(Polymorphism)  是面向对象的核心特性,指「同一行为在不同对象上表现出不同的实现」:

  • 前提:存在继承关系(子类继承父类);
  • 核心:子类重写父类方法;
  • 表现:父类引用指向子类对象,调用方法时实际执行的是子类的重写实现(而非父类方法)。

Scala 中多态的实现方式:

  • 「子类型多态」(最常用):基于继承和重写,如父类 Animal 引用指向 Dog/Cat 实例;
  • 「特质多态」:基于特质(Trait)的混入,类似多继承的多态;
  • 「参数多态」:基于泛型(如 List[T]),与继承无关。

2. 多态的语法与示例

核心语法

// 父类引用指向子类对象(多态的关键)
val 父类变量名: 父类类型 = new 子类类型(参数)

// 调用方法时,实际执行子类的重写实现
父类变量名.方法名()

代码示例(基于前面的 Animal 类)

object PolymorphismDemo extends App {
  // 父类:Animal(同示例 1)
  class Animal(val name: String) {
    def sound(): Unit = println(s"$name 发出声音")
  }

  // 子类:Dog(重写 sound)
  class Dog(name: String) extends Animal(name) {
    override def sound(): Unit = println(s"小狗 $name 汪汪叫")
  }

  // 子类:Cat(重写 sound)
  class Cat(name: String) extends Animal(name) {
    override def sound(): Unit = println(s"小猫 $name 喵喵叫")
  }

  // 子类:Bird(重写 sound)
  class Bird(name: String) extends Animal(name) {
    override def sound(): Unit = println(s"小鸟 $name 叽叽叫")
  }

  // 多态核心:父类引用指向不同子类对象
  val animal1: Animal = new Dog("旺财")
  val animal2: Animal = new Cat("咪咪")
  val animal3: Animal = new Bird("啾啾")

  // 调用方法:实际执行子类的重写实现(多态表现)
  animal1.sound() // 输出:小狗 旺财 汪汪叫(执行 Dog 的 sound)
  animal2.sound() // 输出:小猫 咪咪 喵喵叫(执行 Cat 的 sound)
  animal3.sound() // 输出:小鸟 啾啾 叽叽叫(执行 Bird 的 sound)

  // 批量处理:利用多态简化代码(核心优势)
  def makeSound(animals: Animal*): Unit = {
    animals.foreach(_.sound()) // 遍历所有动物,统一调用 sound 方法
  }

  println("\n批量调用动物叫声:")
  makeSound(animal1, animal2, animal3)
  // 输出:
  // 小狗 旺财 汪汪叫
  // 小猫 咪咪 喵喵叫
  // 小鸟 啾啾 叽叽叫
}

3. 多态的核心优势

  • 「代码复用与扩展」:批量处理不同子类对象时,无需针对每个子类写单独逻辑(如 makeSound 方法可处理所有 Animal 子类);
  • 「松耦合」:父类定义统一接口,子类实现具体逻辑,新增子类时无需修改原有代码(符合「开闭原则」);
  • 「灵活性」:同一父类引用可动态指向不同子类对象,执行不同逻辑。

4. 多态的限制:父类引用无法访问子类特有方法

父类引用指向子类对象时,只能调用父类中定义的方法 / 属性,无法直接访问子类特有方法(需通过「类型转换」实现):

val animal: Animal = new Dog("旺财")
animal.sound() // 正常:父类有 sound 方法
// animal.fetch() // 编译报错:Animal 类无 fetch 方法(子类特有)

// 类型转换:将父类引用转为子类类型(需确保实际是子类对象,否则抛异常)
if (animal.isInstanceOf[Dog]) { // 判断类型
  val dog = animal.asInstanceOf[Dog] // 强制转换
  dog.fetch() // 正常:转换后可访问子类特有方法
}

5. 特质多态(补充)

Scala 不支持多继承,但可通过「特质(Trait)」实现类似多继承的多态(一个类可混入多个特质):

object TraitPolymorphismDemo extends App {
  // 特质 1:可飞
  trait Flyable {
    def fly(): Unit
  }

  // 特质 2:可游泳
  trait Swimmable {
    def swim(): Unit
  }

  // 类:鸭子(继承 Animal 类,混入 Flyable 和 Swimmable 特质)
  class Duck(name: String) extends Animal(name) with Flyable with Swimmable {
    override def sound(): Unit = println(s"鸭子 $name 嘎嘎叫")
    override def fly(): Unit = println(s"鸭子 $name 正在飞")
    override def swim(): Unit = println(s"鸭子 $name 正在游")
  }

  // 多态:特质引用指向实现类对象
  val flyable: Flyable = new Duck("唐老鸭")
  flyable.fly() // 输出:鸭子 唐老鸭 正在飞

  val swimmable: Swimmable = new Duck("唐老鸭")
  swimmable.swim() // 输出:鸭子 唐老鸭 正在游

  val animal: Animal = new Duck("唐老鸭")
  animal.sound() // 输出:鸭子 唐老鸭 嘎嘎叫
}

总结

继承核心要点

  1. 单继承:一个子类只能直接继承一个父类,特质可多混入;
  2. 构造规则:子类必须先调用父类构造,通过 extends 父类(参数) 传递;
  3. 重写规则:override 关键字必填(非抽象成员),final 禁止继承 / 重写;
  4. 访问权限:private(父类私有)→ protected(子类可访问)→ public(默认,全访问)。

多态核心要点

  1. 前提:继承 + 子类重写父类方法;
  2. 表现:父类引用指向子类对象,调用方法时执行子类实现;
  3. 优势:代码复用、松耦合、灵活扩展;
  4. 限制:父类引用无法直接访问子类特有方法,需类型转换。

Scala 的继承与多态既保留了面向对象的核心特性,又通过「特质」和「函数式编程」补充了灵活性,是构建复杂系统的基础。