类的继承

46 阅读5分钟

继承的概念和基本语法

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

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

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

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

继承的好处之复用代码

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

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

object Class01 {
  class Boss {
    def power():Unit = {
      println("Boss有权力....")
    }
    var car ="豪车"
  }
  class NoBody extends Boss{

  }

  def main(args: Array[String]): Unit = {
    val b = new NoBody()
    println(b.car)
    b.power()
  }
}

继承的方法重写

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

object Class01 {
  class Boss {
    def power():Unit = {
      println("Boss有权力....")
    }
    var car ="豪车"
  }
  //     子类    继承     父类
  class NoBody extends Boss{
    //子类自己的方法,与父类方法不同名
    def run():Unit = {
    }

    override def power(): Unit = {
      println("老莫,我想吃鱼了")
    }
  }

  def main(args: Array[String]): Unit = {
    val b = new NoBody()
    println(b.car)
    b.power()
    b.run()
  }
}

继承与多态

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

通过代码来理解多态-写一个函数,它的参数类型是父类。

/*
*1.类和对象
* 2.new 创建对象, new XXXX()
* 3.构造函数,构造器
* 4.辅助构造器 this
* 5.private,override,toString,this
* 
* 
* 父类和子类的构造器调用顺序:先调用父类的构造器,再调用子类的构造器
 */

object Class02 {
 class Father() {
   println("Father 构造器被调用")
 }
  class Son extends Father(){
    println("Son 构造器被调用")
  }

  def main(args: Array[String]): Unit = {
    // new Father()
    new Son()
  }
}

构造器的调用顺序

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

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

object Class03 {
 class Dog(var name:String,var age:Int) {
   println(s"Dog${name},${age}构造器被调用")
 }
  //子类 中华田园犬
  //子类带参构造器
  //1.继承父类的属性。不用写val,var 修饰符
  //父类构造器
  //直接传入参数,不用写属性的类型
  class RuralDog (name:String,age:Int) extends Dog(name,age){
    println("RuralDog的构造器...")
  }

  def main(args: Array[String]): Unit = {
    new RuralDog("旺财",3)
  }
}

带参构造器的写法

object Class04 {
 // 定义 Point 类
 class Point(var x: Double, var y: Double) {
  def getDist(): Double = {
   Math.sqrt(x*x + y*y)
  }
  // 方法1:判断所在象限
  def whereAmI(): String = {
   if (x > 0 && y > 0) {
    "第1象限"
   } else if (x < 0 && y > 0) {
    "第2象限"
   } else if (x < 0 && y < 0) {
    "第3象限"
   } else if (x > 0 && y < 0) {
    "第4象限"
   } else {
    "在坐标轴上"
   }
  }
  def fromPoint(other:Point):Double={

   val dx = other.x - x
   val dy = other.y - y
   Math.sqrt(dx*dx+dy*dy)
 }
  override def toString:String=s"Point(${x},${y})"
  override def equals(obj:Any):Boolean={

  }
  class LabelPoint() extends Point{
   
  }
 

 def main(args: Array[String]): Unit = {
 var p1 = new Point(1.0,4.0)
 val p2 = new Point(3.0,4.0)
 println(p1.whereAmI())
  println(p1.getDist())
  println(p1.fromPoint(p2))
  println(p1)
 }
 }

你正在设计一个二维游戏的界面,需要用很多的点来表示游戏中的角色。

设计一个Point类,其x和y可以通过构造器提供。

它有几个方法:

1. 方法1: 计算自己在哪个象限。 whereAmI():String

2. 方法2: 计算和坐标原点的距离。getDist():Double

3. 方法3: 计算与另一个点的距离。fromPoint(other:Point):Double

4. 方法4: 重写equals 判断是否是同一个点(x和y都相等就是同一个点)。

5. 方法5: 重写toString,更友好的输出点的信息。

再设计一个子类LabelPoint它来继承Point类,其构造器接收一个标签值和x,y坐标。

例如: New LabelPoint("black",1,2)

object Class05 {
 // 定义 Point 类
 class Point(var x: Double, var y: Double) {
  // 方法2:计算与坐标原点的距离
  def getDist(): Double = {
   Math.sqrt(x * x + y * y)
  }

  // 方法1:判断所在象限
  def whereAmI(): String = {
   if (x > 0 && y > 0) {
    "第1象限"
   } else if (x < 0 && y > 0) {
    "第2象限"
   } else if (x < 0 && y < 0) {
    "第3象限"
   } else if (x > 0 && y < 0) {
    "第4象限"
   } else {
    "在坐标轴上"
   }
  }

  // 方法3:计算与另一个点的距离
  def fromPoint(other: Point): Double = {
   val dx = other.x - this.x
   val dy = other.y - this.y
   Math.sqrt(dx * dx + dy * dy)
  }

  // 方法5:重写toString,友好输出
  override def toString: String = s"Point(${x}, ${y})"

  // 方法4:重写equals,判断是否为同一个点(修复类型检查漏洞)
  override def equals(obj: Any): Boolean = obj match {
   case other: Point => this.x == other.x && this.y == other.y
   case _ => false  // 非Point类型直接返回false
  }
 }

 // 子类LabelPoint:继承Point,增加标签属性
 class LabelPoint(val label: String, x: Double, y: Double) extends Point(x, y) {
  // 重写toString,包含标签信息
  override def toString: String = s"LabelPoint(label='${label}', x=${x}, y=${y})"

  // 重写equals:标签和坐标都相等才是同一个点
  override def equals(obj: Any): Boolean = obj match {
   case other: LabelPoint =>
    this.label == other.label && this.x == other.x && this.y == other.y
   case _ => false
  }
 }

 // 测试主方法(修复格式化语法)
 def main(args: Array[String]): Unit = {
  // 测试Point类
  val p1 = new Point(1.0, 4.0)
  val p2 = new Point(3.0, 4.0)
  val p3 = new Point(1.0, 4.0)
  println("=== Point类测试 ===")
  println(s"p1所在象限:${p1.whereAmI()}")  // 第1象限
  // 修复方式1:用 f 字符串格式化(支持 .2f 格式符)
  println(f"p1到原点距离:${p1.getDist()}%.2f")  // 4.12(保留2位小数)
  println(f"p1到p2的距离:${p1.fromPoint(p2)}%.2f")  // 2.00
  println(s"p1 == p2?${p1 == p2}")  // false
  println(s"p1 == p3?${p1 == p3}")  // true
  println(s"p1的toString:${p1}")  // Point(1.0, 4.0)

  println("\n=== LabelPoint类测试 ===")
  // 测试LabelPoint类
  val lp1 = new LabelPoint("black", 1, 2)
  val lp2 = new LabelPoint("white", 1, 2)
  val lp3 = new LabelPoint("black", 1, 2)
  println(s"lp1所在象限:${lp1.whereAmI()}")  // 第1象限(继承自Point的方法)
  // 修复方式2:用 formatted 方法格式化(兼容 s 字符串)
  println(s"lp1到原点距离:${p1.getDist().formatted("%.2f")}")  // 4.12
  println(s"lp1到lp2的距离:${lp1.fromPoint(lp2).formatted("%.2f")}")  // 0.00
  println(s"lp1 == lp2?${lp1 == lp2}")  // false(标签不同)
  println(s"lp1 == lp3?${lp1 == lp3}")  // true(标签和坐标都相同)
  println(s"lp1的toString:${lp1}")  // LabelPoint(label='black', x=1.0, y=2.0)

  // 测试跨类型比较(确保equals逻辑正确)
  println(s"\n跨类型比较:p1 == lp1?${p1 == lp1}")  // false(类型不同)
 }
}