Scala中类的继承

51 阅读5分钟

(一)继承的概念和基本语法

定义:在原有类的基础上定义一个新类,原有类称为父类,新类称为子类。

class 子类名 extends 父类名 { 类体 }

好处:复用代码和实现多态。复用代码:子类可以继承父类的特性。多态 子类可以在自己内部实现父类没有的特性。

语法: 假设定义Parents为父类,C1为子类,通过关键字extends子类便可以继承父类的特性,相关代码为:

class C1(参数可选) extends Parents(参数可选){ }

(二)继承的好处之复用代码

那一个类继承了另一个类之后,有什么好处呢?

一旦我们完成了继承,就可以直接在子类的对象中调用父类的方法。

import java.io.FileWriter

object Main {
    /*
    * 继承
    * extends
    * 好处:不劳而获
    *
    */
    
    class Animal() {
        def eating(): Unit = {
            println("Animal eating")
        }
    }

    // Dog 继承了 Animal
    class Dog extends Animal() {
    }

    def main(args: Array[String]): Unit = {
        val dog1 = new Dog()
        dog1.eating() // 直接可以使用父类的方法
    }
}

继承的特点:Dog就直接具备了animal的功能eating。

image.png

这个继承示例的关键点:

  1. 继承语法class Dog extends Animal()
  2. 方法继承:子类 Dog 自动获得父类 Animal 的所有非私有方法
  3. "不劳而获" :子类不需要重新实现 eating 方法,直接使用父类的实现
  4. 代码复用:通过继承可以避免代码重复

这就是面向对象编程中继承的核心好处 - 子类可以复用父类的功能。

(三)继承的方法重写

当子类从父类继承的方法不能满足需要时,子类需要有自己的行为,怎么办?此时使用使用 override 可以重写父类的方法。

object Main {
    /*
    * 继承
    * extends
    * 好处:不劳而获
    *
    * 问题:
    * 如果子类觉得父类的方法并不是自己要的,如何定义自己的方法呢?
    */
    
    class Animal() {
        def eating(): Unit = {
            println("Animal eating")
        }
    }

    // Dog 继承了 Animal
    class Dog extends Animal() {
        // 在子类中重写(覆盖)父类的方法
        override def eating(): Unit = {
            println("我是狗,我有自己的吃饭的方式!!")
        }
    }

    def main(args: Array[String]): Unit = {
        val dog1 = new Dog()
        dog1.eating() // 调用自己的eating方法!
    }
}

这个方法重写示例的关键点:

  1. 方法重写:使用 override 关键字重写父类方法
  2. 自定义实现:子类可以提供与父类不同的方法实现
  3. 多态性:相同的 eating() 方法调用,在不同子类中有不同行为
  4. 语法要求:必须使用 override 关键字明确表示重写

这样既保持了继承的代码复用好处,又允许子类根据自身需求定制特定的行为。

object Main {
    /*
    * 继承
    * extends
    * 好处:不劳而获
    *
    * 问题:
    * 如果子类觉得父类的方法并不是自己要的,如何定义自己的方法呢?
    *
    * 1. override 重写
    * 2. super 在子类内部,通过super来访问父类
    */
    
    class Animal() {
        def run(): Unit = {
        }
        
        def eating(): Unit = {
            println("Animal eating")
        }
    }

    // Dog 继承了 Animal
    class Dog extends Animal() {
        // 在子类中重写(覆盖)父类的方法
        override def eating(): Unit = {
            // 调用父类的方法?
            // 在子类内部,通过super来访问父类
            super.eating()
            println("我是狗,我有自己的吃饭的方式!")
        }
    }

    def main(args: Array[String]): Unit = {
        val dog1 = new Dog()
        dog1.eating() // 调用自己的eating方法!
    }
}

这个 super 使用示例的关键点:

  1. 方法重写:使用 override 关键字重写父类方法
  2. 调用父类方法:使用 super.eating() 调用父类的原始实现
  3. 扩展功能:在调用父类方法的基础上,添加子类特有的功能
  4. 代码复用:既复用了父类的逻辑,又添加了新的行为

这样可以在重写方法时,既保留父类的核心功能,又根据子类需求进行功能扩展。

(四)继承与多态

面向对象的三个特点:封装  继承 多态 。同一操作作用于不同的对象, 可以有不同的解释,产生不同的执行结果,这就是多态性。

object Main {
    
    class Animal() {
        def eating(): Unit = {
            println("Animal eating")
        }
    }

    // Dog 继承了 Animal
    class Dog extends Animal() {
        override def eating(): Unit = {
            println("我是狗,我吃饭大口大口嚼")
        }
    }

    // Cat 继承了 Animal
    class Cat extends Animal() {
        override def eating(): Unit = {
            println("我是猫,我吃饭小口小口吃")
        }
    }

    // 测试函数
    def test(animal: Animal): Unit = {  
        animal.eating()  
    }

    def main(args: Array[String]): Unit = {
        val cat = new Cat()
        val dog = new Dog()

        test(cat)
        test(dog)
    }
}

这个多态性示例的关键点:

  1. 多态性test 函数接收 Animal 类型的参数
  2. 动态绑定:运行时根据实际对象类型调用相应的方法
  3. 统一接口:相同的 animal.eating() 调用,产生不同的行为
  4. 扩展性:可以轻松添加新的动物子类,无需修改 test 函数

这展示了面向对象编程的核心特性 - 多态,允许使用父类类型的引用来调用子类特有的方法实现。

(五)处理构造器的调用顺序

当我们实例化子类的对象时,是否需要调用父类的构造器? 是否需要调用子类的构造器?

代码验证调用顺序:父类的主构造器->子类主构造器->子类的辅助构造器

object Main {
    /*
    * 存在继承关系的时候,构造器的调用顺序?
    * 父类构造器 → 子类构造器
    *
    */
    
    class Animal() {
        println("父类构造器被调用......")
    }

    // Dog 继承了 Animal
    class Dog extends Animal() {
        println("子类:Dog 构造器被调用......")
    }

    // Puppy 继承了 Dog
    class Puppy extends Dog() {
        println("子类:Puppy 构造器被调用......")
    }

    def main(args: Array[String]): Unit = {
        new Puppy() // new会自动调用构造器去生成对象
    }
}

这个构造器调用顺序示例的关键点:

  1. 构造器调用顺序:从最顶层的父类开始,逐级向下调用

  2. 继承链Animal → Dog → Puppy

  3. 执行顺序

    • 先调用 Animal 构造器
    • 再调用 Dog 构造器
    • 最后调用 Puppy 构造器
  4. 自动调用:创建子类对象时,会自动调用父类构造器

这验证了在继承关系中,构造器的调用顺序确实是:父类构造器 → 子类构造器