Scala学习笔记9: 继承

第九章 继承

在Scala中, 继承(Inheritance) 是面向对象编程的重要概念, 允许一个类 (子类) 继承另一个类 (父类) 的特性和行为 ;

Scala中的继承通过 extends 关键字实现;

子类可以继承父类的字段、方法和特制 (traits) , 并可以重写父类的方法 .

1- 扩展类和重写方法

在Scala中, 扩展类 (Extending Classes) 是通过使用 extends 关键字来实现的 ;

当一个类扩展另一个类时, 他会继承父类的特性 (字段, 方法等) 并可以重写父类的方法 ;

当子类继承父类并重写的方法是, 需要使用 override 关键字来标记重写的方法 ;

示例:

  // 定义父类
  class Animal {
    def sound(): Unit = {
      println("Animal makes a sound !")
    }
  }

  // 定义子类, 扩展自 Animal
  class Dog extends Animal {
    override def sound(): Unit = {
      println("Dog barks !")
    }
  }


  def main(args: Array[String]): Unit = {
    // 创建子类对象
    val dog = new Dog()
    // 调用子类方法
    dog.sound() // 输出: Dog barks !
  }
  • 在上面的示例中, Dog 类扩展自 Animal 类, 并重写了 sound() 方法 ;
  • 通过扩展类, Dog 类继承了 Animal 类的特性, 并可以根据需要进行定制化 ;
  • 这种灵活性使得Scala中的继承和重写方法非常强大 ;

需要注意的是, 在Scala中, 一个类只能扩展一个类(单继承) , 到那时可以混入多个特质(traits) 来实现多重继承和代码复用 .

2- 类型转换和检查

在Scala中, 继承中的类型转换和检查可以通过 isInstanceOfasInstanceOf 方法来实现 ;

示例:

  // 定义父类
  class Animal {
    def sound(): Unit = {
      println("Animal makes a sound !")
    }
  }

  // 定义子类, 扩展自 Animal
  class Dog extends Animal {
    def bark(): Unit = {
      println("Dog barks !")
    }
  }


  def main(args: Array[String]): Unit = {
    // 创建子类实例
    val animal: Animal = new Dog()

    // 类型检查
    if (animal.isInstanceOf[Dog]) {
      // 类型转换
      val dog: Dog = animal.asInstanceOf[Dog]
      dog.bark() // 输出: Dog barks !
    } else {
      println("Not a Dog !")
    }

  }
  • 在上面的示例中, 我们首先创建了一个 Dog 类的实例, 并将其赋值给一个 Animal 类型的变量 ;
  • 然后, 我们使用 isInstanceOf 方法检查变量的类型是否是 Dog , 如果是, 则使用 asInstanceOf 方法将其转换为 Dog 类型, 并调用 bark() 方法 ;
  • 如果类型不是 Dog , 则输出 “Not a Dog !”

需要注意的是, 在进行类型转换时, 应该谨慎处理可能出现的类型不匹配情况, 避免抛出 ClassCastException 异常 ;

因此, 在进行类型转换之前最好先进行类型检查 .

3- 受保护字段

在Scala中, 受保护字段 (Protected Fields) 是指只能在当前类、子类或伴生对象中访问的字段 ;

通过使用 protected 关键字来声明字段, 可以实现字段的受保护访问权限 ;

示例:

  // 定义父类
  class Animal {
    protected var name: String = "Animal"

    def getName(): String = {
      name
    }
  }

  // 定义子类, 继承自 Animal
  class Dog extends Animal {
    def setName(newName: String): Unit = {
      name = newName
    }
  }


  def main(args: Array[String]): Unit = {
    // 创建子类实例
    val dog = new Dog()
    println(dog.getName()) // 输出: Animal
    dog.setName("Buddy")
    println(dog.getName()) // 输出: Buddy
  }
  • 在上面的示例中, Animal 类中的 name 字段被声明为受保护字段, 只能在 Animal 类及其子类中访问 ;
  • 子类 Dog 继承了 Animal 类, 并可以访问和修改 name 字段 ;
  • 通过这种方式, 受保护字段可以提供一定程度的访问控制, 保护字段不被外部类直接访问 ;

4- 超类的构造

在Scala中, 子类在构造时需要调用超类(父类) 的构造器 ;

子类可以通过 extends 关键字继承超类, 并在子类的构造器中调用超类的构造器 ;

示例:

  // 定义父类(超类)
  class Person(val name: String, val age: Int) {
    def displayInfo() = {
      println(s"Name: $name, Age: $age")
    }
  }

  // 定义子类, 继承父类 Person
  class Employee(name: String, age: Int, val role: String) extends Person(name, age) {
    def displayRole() = {
      println(s"Role: $role")
    }
  }

  def main(args: Array[String]): Unit = {
    // 创建子类实例
    val employee = new Employee("John Doe", 30, "Software Engineer")
    // 调用父类方法
    employee.displayInfo() // Output: Name: John Doe, Age: 30
    // 调用子类方法
    employee.displayRole() // Output: Role: Software Engineer
  }
  • 在上面的示例中, Employee 类继承自 Person 类, 并在其构造器中调用了 Person 类的构造器 ;
  • 通过这种方式, 子类可以通过调用超类(父类) 构造器来初始化超类的字段 ;
  • 这种机制确保了在构造子类实例时, 超类的构造器也会被正确调用 .

5- 重写字段和方法

在Scala中, 子类可以重写父类的 def 方法、val 字段和 var 字段 ;

当子类重写父类的 def 方法时, 子类可以提供新的实现 ;

当子类重写父类的 val 字段和 var 字段时, 子类可以重新定义字段的值 ;

示例:

  // 定义父类
  class Person {
    def greet(): Unit = {
      println("Hello, I am a Person !")
    }

    val name: String = "Person"
    var age: Int = 26
  }

  // 定义子类, 继承父类, 重写父类的方法和字段
  class Student extends Person {
    override def greet(): Unit = {
      println("Hello, I am a Student !")
    }

    override val name: String = "Student"
    //override var age: Int = 18 //  variable age cannot override a mutable variable
    age = 18
  }

  def main(args: Array[String]): Unit = {
    // 创建子类实例
    val student = new Student()
    student.greet() // Output: Hello, I am a Student !
    println(student.name) // Output: Student
    println(student.age) // Output: 18
  } 
  • 在上面的示例中, Employee 类继承自 Person类, 并重写了 greet() 方法 和 name 字段 ;
  • 通过 override 关键字, 子类 Employee 提供了新的 greet() 实现, 以及重新定义了 agename 字段的值 ;
  • 当创建 Employee 类的实例时, 调用 greet() 方法 将输出子类中重写的内容, 并访问 agename 字段将返回子类中重写的值 ;
  • 注意: 在Scala中, 重写一个可变(mutable) 的变量 (var) 是不允许的 , 子类无法直接重写父类的 var 字段 ;
  • 子类继承父类的 var 字段, 并可以在子类中直接使用它 ;

6- 匿名子类

在Scala中, 可以使用匿名子类 (Anonymous Subclass) 来创建没有显示命名的子类 ;

匿名子类通常用于临时性的扩展或重写父类的行为, 而无需为其定义一个具名的类 ;

示例:

  // 定义父类
  class Person(val name: String) {
    def greet(): Unit = {
      println(s"Hello, My name is $name")
    }
  }

  // 创建匿名子类并重写父类方法
  val anonymousPerson = new Person("Anonymous") {
    override def greet(): Unit = {
      println(s"Hello,I am $name")
    }
  }

  def main(args: Array[String]): Unit = {
    
    // 调用匿名子类方法
    anonymousPerson.greet() // Output: Hello,I am Anonymous
  }
  • 上面的示例中, 我们定义了一个 Person 类, 然后使用匿名子类重写了 greet() 方法 ;
  • 通过创建一个没有显示类名的子类实例, 我们可以在其中提供新的实现 ;
  • 这种方式适用于需要临时性的定制化行为或重写父类方法的情况 ;

匿名子类在Scala中是一种灵活且常用的技术, 可以帮助简化代码结构并实现临时性的定制化需求 .

7- 抽象类

在Scala中, 抽象类 (Abstract Classes) 是一种类似于 Java中抽象类的概念 ;

抽象类可以包含抽象方法 (没有具体实现的方法) 和非抽象方法 (有具体实现的方法) ;

与 Traits 相比, 抽象类可以包含构造函数参数, 而 Traits 不支持构造函数参数 ;

通常情况下, 当需要创建一个基类, 该基类需要构造函数参数或者需要被 Java 代码调用时, 可以使用抽象类 ;

示例:

  // 定义一个抽象类
  abstract class Animal(name: String) {
    // 抽象方法, 子类必须实现
    def makeSound(): Unit

    // 非抽象方法, 具体实现
    def eat(food: String): Unit = {
      println(s"$name is eating $food")
    }
  }

  // 定义一个子类, 继承抽象类
  class Dog(name: String) extends Animal(name) {
    // 实现抽象方法
    override def makeSound(): Unit = {
      println("Woof Woof!")
    }
  }

  def main(args: Array[String]): Unit = {
    // 创建一个Dog对象
    val dog = new Dog("Buddy")
    // 调用子类的方法
    dog.makeSound() // Output: Woof Woof!
    dog.eat("bones") // Output: Buddy is eating bones
  }
  • 在上面的示例中, Animal 类是一个抽象类, 包含一个抽象方法 makeSound() 和一个非抽象方法 eat() ;
  • 子类 Dog 继承自 Animal 类, 并实现了抽象方法 makeSound() ;
  • 通过创建 Dog 类的示例, 我们可以调用抽象类中定义的方法 ;

抽象类在Scala中是一种重要的概念, 用于实现类似于 Java中的抽象类的功能 .

8- 抽象字段

在Scala中, 可以定义抽象字段 (Abstract Fields) 来要求字了提供具体的字段实现 ;

抽象字段可以是 val (不可变) 或 var (可变) ;

在抽象类中声明的抽象字段自行在子类中被具体化 ;

示例:

  // 定义一个抽象类
  abstract class Animal(name: String) {
    // 抽象的不可变字段
    val greeting: String

    // 抽象的可变字段
    var age: Int

    // 具体方法
    def sayHello(): Unit = {
      println(s"$greeting, my name is $name")
    }

    // 抽象方法
    def displayDetails(): Unit
  }

  // 定义一个子类, 继承抽象类
  class Dog(name: String) extends Animal(name) {
    // 实现抽象字段
    val greeting: String = "Hello"
    var age: Int = 3

    // 实现抽象方法
    override def displayDetails(): Unit = {
      println(s"My name is $name, I am $age years old.")
    }
  }

  def main(args: Array[String]): Unit = {
    // 创建一个Dog对象
    val dog = new Dog("Buddy")
    // 调用子类的方法
    dog.sayHello() // 输出: Hello, my name is Buddy
    dog.displayDetails() // 输出: My name is Buddy, I am 3 years old.
  }
  • 在上面的示例中, Animal 类是一个抽象类, 包含一个抽象的不可变字段 greeting 和一个抽象的可变字段 age ;
  • 子类 Dog 继承 Animal 类, 并提供了具体的字段实现 ;
  • 通过创建 Dog 类的实例, 我们可以调用抽象类中定义的方法, 并访问具体的字段值 ;

抽象字段在 Scala中运行你定义需要再子类中提供具体实例的字段 ;

这种机制有助于实现灵活的类设计和继承结构 .

9- Scala继承层级

在Scala中, 继承层级的根是 Any , 它是所有其他类的超类 ;

在Scala中, Any 有两个直接子类: AnyValAnyRef ;

AnyVal 是所有内建值类型的超类, 例如 IntDoubleBoolean 等 ;

AnyRef 则是所有引用类型(类) 的超类, 类似于 Java 中的 Object ;

在Scala中, 几乎所有的类都是 AnyRef 的子类, 因为他们都是引用类型 ;

AnyRef 继承在 Any , 而 Any 又是所有类的超类 ;

这种继承层级关系确保了在Scala中所有类都是有一些通用的方法和属性, 例如 equalshashCode 等 ;

示例, 展示 AnyAnyRefAnyVal 之间的继承关系:

    // Any 是Scala中的顶级父类,所有类的父类都是 Any。
    val anyValue: Any = "Hello"
    val anyRefValue: AnyRef = "Hello"
    val anyValValue: AnyVal = 12

    // AnyRef 是Scala中的顶级父类,所有引用类型的父类都是 AnyRef。
    val stringValue: String = "Hello"
    val listValue: List[Int] = List(1, 2, 3)

    // AnyVal 是Scala中的顶级父类,所有值类型的父类都是 AnyVal。
    val intValue: Int = 12
    val doubleValue: Double = 12.0
  • 在上面的示例中, Any 是所有类的超类, AnyRef 是所有引用类型的超类, 而 AnyVal 是所有值类型的超类 ;
  • 通过这种继承层级, Scala提供了一种统一的方式来处理不同类型的值和引用 ;

继承层级中的 AnyAnyRefAnyVal 是Scala中非常重要的概念, 有助于理解Scala中类之间的关系和类型系统 .

10- 对象相等性

在Scala中, 对象相等性有两种概念: 引用相等性 (Reference Equality) 和结构相等性 (Structural Equality) ;

  1. 引用相等性: 引用相等性是指两个对象是否指向内存中的同一位置 ; 在Scala中, 可以使用 eqne 方法来检查两个对象是否引用同一个内存地址 .

    例如:

        val a = "hello"
        val b = "hello"
        val c = new String("hello")
        println(a eq b) // 输出: true,因为字符串字面量通常会被优化为共享实例
        println(a eq c) // 输出: false,尽管内容相同,但 c 是新创建的对象
    
  2. 结构相等性: 结构相等性是指两个对象的内容是否相等 ;

    • 在Scala中, 可以使用 ==!= 运算符来检查两个对象的内容是否相等 ;
    • 对于引用类型, == 默认调用 equals 方法进行比较 ;

    示例:

        val a = "hello"
        val b = "hello"
        val c = new String("hello")
        println(a == b) // 输出: true
        println(a == c) // 输出: true
        println(a.equals(b)) // 输出: true
        println(a.equals(c)) // 输出: true
    
  • 除了上述方法外, Scala还提供了 equals 方法和 hashCade 方法用于自定义对象的相等性比较;
  • 如果需要自定义对象的相等性逻辑, 可以重写 equals 方法和 hashCode 方法 ;

总的来说, 在Scala中, 对象的相等性可以通过引用相等性和结构相等性来进行比较, 开发人员可以根据具体需求选择合适的比较方式 .

end

  • 12
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值