Scala继承和抽象,trait,样例类

继承:

实际开发中, 我们发现好多类中的内容是相似的(例如: 相似的属性和行为), 每次写很麻烦. 于是我们可以把这些相似的内容提取出来单独的放到一个类中(父类), 然后让那多个类(子类)和这个类(父类)产生一个关系, 从而实现子类可以访问父类的内容, 这个关系就叫: 继承.因为scala语言是支持面向对象编程的,我们也可以使用scala来实现继承,通过继承来减少重复代码。

格式 

class/object A类 extends B类 {	
    ..
}

叫法

  • 上述格式中, A类称之为: 子类, 派生类.

  • B类称之为: 父类, 超类, 基类.

代码示例:

使用非继承版:

package test6

object Test1 {
  class Teacher {
    var name = ""
    var age = 0

    def eat() = println("老师在吃饭")
  }

  class Student {
    var name = ""
    var age = 0

    def eat() = println("学生在吃饭")
  }

  def main(args: Array[String]): Unit = {
    val teacher = new Teacher
    teacher.name = "王老师"
    teacher.age = 23
    println(teacher.name, teacher.age)
    teacher.eat()
    println("-" * 15)

    val student = new Student
    student.name = "张三"
    student.age = 21
    println(student.name, student.age)
    student.eat()
  }

}

 使用继承版:(可以使用父类方法,也可以重写父类方法)

package test6

object Test2 {
  class Person {
    var name = ""
    var age = 0

    def eat() = println("人要吃饭")
  }

  class Teacher extends Person {
    super.eat()

    override def eat(): Unit = println("老师吃饭")
  }

  class Student extends Person {
    super.eat()

    override def eat(): Unit = println("学生吃饭")
  }

  def main(args: Array[String]): Unit = {
    val teacher = new Teacher
    teacher.name = "刘老师"
    teacher.age = 32
    println(teacher.name, teacher.age)
    teacher.eat()
    println("-" * 15)

    val student = new Student
    student.name = "张三"
    student.age = 23
    println(student.name, student.age)
    student.eat()
  }
}

单例对象继承

在Scala中, 单例对象也是可以继承类的.

package test6

object Test3 {
  class Person {
    var name = ""

    def sayHello() = println("hello")
  }

  object Student extends Person

  def main(args: Array[String]): Unit = {
    Student.name = "张三"
    println(Student.name)
    Student.sayHello()
  }
}

方法重写

子类中出现和父类一模一样的方法时, 称为方法重写. Scala代码中可以在子类中使用override来重写父类的成员,也可以使用super来引用父类的成员.

  • 子类要重写父类中的某一个方法,该方法必须要使用override关键字来修饰

  • 可以使用override来重写一个val字段.

  • 使用super关键字来访问父类的成员方法

  • 父类用var修饰的变量, 子类不能重写.

代码示例:

  • 定义Person类, 属性(姓名, 年龄), 有一个sayHello()方法.
  • 然后定义Student类继承Person类, 重写Person类中的字段和方法, 并测试.
package test6

object Test4 {
  class Person {
    var name = "张三"
    val age = 23

    def sayHello() = println("hello")
  }

  class Student extends Person {
    //    override var name = "李四"  //这样写会报错, 子类不能重写父类用var修饰的变量.
    override val age = 56

    override def sayHello(): Unit = {
      super.sayHello()
      println("hello student")
    }
  }

  def main(args: Array[String]): Unit = {
    val student = new Student
    println(student.name, student.age)
    student.sayHello()
  }
}

类型推断

在scala中,如何来进行类型判断呢?

有两种方式:

  • isInstanceOf:判断对象是否为指定类的对象

  • getClass/classOf: 将对象转换为指定类型

1.isInstanceOf,asInstanceOf 

格式:

// 判断对象是否为指定类型
val trueOrFalse:Boolean = 对象.isInstanceOf[类型]

// 将对象转换为指定类型
val 变量 = 对象.asInstanceOf[类型]

 代码示例:

package test6

object Test5 {
  class Person

  class Student extends Person {
    def sayHello() = println("hello scala..")
  }

  def main(args: Array[String]): Unit = {
    val p: Person = new Student
    //    p.sayHello() 这么写会报错,父类引用不能直接访问子类特有成员

    //    判断其是否是Student,Person类型的对象 都是true
    println(p.isInstanceOf[Person])
    println(p.isInstanceOf[Student])

    val p1 = p.asInstanceOf[Student] //将Person类型对象转换为Student类型对象
    p1.sayHello() //现在可以使用sayHello这个方法了
  }

}

2.getClass和classOf

isInstanceOf 只能判断对象是否为指定类以及其子类的对象,而不能精确的判断出: 对象就是指定类的对象。如果要求精确地判断出对象的类型就是指定的数据类型,那么就只能使用 getClass 和 classOf 来实现.

代码示例:

package test6

object Test6 {
  class Person

  class Student extends Person

  def main(args: Array[String]): Unit = {
    val p: Person = new Student
    println(p.isInstanceOf[Person]) //true
    println(p.isInstanceOf[Student]) //true
    println(p.getClass == classOf[Person]) //false
    println(p.getClass == classOf[Student]) //true
  }
}

抽象类

格式:

// 定义抽象类
abstract class 抽象类名 {
  // 定义抽象字段
  val/var 抽象字段名:类型
  // 定义抽象方法
  def 方法名(参数:参数类型,参数:参数类型...):返回类型
}

代码示例:

package test6

import com.sun.xml.internal.ws.message.saaj.SAAJHeader

object Test7 {
  abstract class Shape {
    def area: Double
  }

  class Square(var edge: Double) extends Shape {
    override def area: Double = edge * edge
  }

  class Reactangle(var length: Double, var width: Double) extends Shape {
    override def area: Double = length * width
  }

  class Circle(var radius: Double) extends Shape {
    override def area: Double = Math.PI * radius * radius
  }

  def main(args: Array[String]): Unit = {
    val s1: Shape = new Square(2)
    val s2: Shape = new Reactangle(2, 3)
    val s3: Shape = new Circle(2)

    println(s1.area)
    println(s2.area)
    println(s3.area)
  }
}

抽象字段

格式:

abstract class 抽象类 {
    val/var 抽象字段:类型
}

代码示例:

package test6

object Test8 {
  abstract class Person {
    val occupation: String
  }

  class Student extends Person {
    override val occupation: String = "学生"
  }

  class Teacher extends Person {
    override val occupation: String = "老师"
  }

  def main(args: Array[String]): Unit = {
    val student = new Student
    println(student.occupation)
    val teacher = new Teacher
    println(teacher.occupation)
  }
}

匿名内部类

匿名内部类是继承了类的匿名的子类对象,它可以直接用来创建实例对象。Spark的源代码中大量使用到匿名内部类。学完这个内容, 对我们查看Spark的底层源码非常有帮助.

格式:

new 类名() {
    //重写类中所有的抽象内容
}

注意: 上述格式中, 如果的类的主构造器参数列表为空, 则小括号可以省略不写.

使用场景

  • 当对对象方法(成员方法)仅调用一次的时候.

  • 可以作为方法的参数进行传递.

代码示例: 

  1. 创建一个Person抽象类,并添加一个sayHello抽象方法

  2. 定义一个show()方法, 该方法需要传入一个Person类型的对象, 然后调用Person类中的sayHello()方法.

  3. 添加main方法,通过匿名内部类的方式来创建Person类的子类对象, 调用Person类的sayHello()方法.

  4. 调用show()方法.

 

package test6

object Test9 {
  abstract class Person {
    def sayhello()
  }

  def show(p: Person) = p.sayhello()

  def main(args: Array[String]): Unit = {
    new Person {
      override def sayhello(): Unit = println("hello scala,当对成员方法仅调用一次的时候")
    }.sayhello()

    val p = new Person {
      override def sayhello(): Unit = println("hello scala,可以作为方法的实际参数进行传递")
    }
    show(p)
  }
}

代码示例:

已知有猫类和狗类, 它们都有姓名和年龄, 都会跑步, 而且仅仅是跑步, 没有什么不同. 它们都有吃饭的功能, 不同的是猫吃鱼, 狗吃肉. 而且猫类独有自己的抓老鼠功能, 狗类独有自己的看家功能, 请用所学模拟该需求.

  1. 定义抽象动物类(Animal), 属性: 姓名, 年龄, 行为: 跑步, 吃饭.

  2. 定义猫类(Cat)继承自动物类, 重写吃饭的方法, 并定义该类独有的抓老鼠的方法.

  3. 定义狗类(Dog)继承自动物类, 重写吃饭的方法, 并定义该类独有的看家的方法.

package test6

object Test10 {
  abstract class Animal {
    var name = ""
    var age = 0

    def run() = println("动物会跑步!")

    def eat() = println("动物吃饭!")
  }

  class Cat extends Animal {
    override def eat(): Unit = println("猫吃鱼!")

    def catchMouse() = println("猫抓老鼠!")
  }

  class Dog extends Animal {
    override def eat(): Unit = println("狗吃肉!")

    def lookHome() = println("狗看家!")
  }

  def main(args: Array[String]): Unit = {
    val cat = new Cat
    cat.name = "汤姆"
    cat.age = 23
    println(cat.name, cat.age)
    cat.eat()
    if (cat.isInstanceOf[Cat]) {
      val c = cat.asInstanceOf[Cat]
      c.catchMouse()
    } else {
      println("您传入的不是猫类!")
    }

    val dog = new Dog
    dog.name = "大黄"
    dog.age = 21
    println(dog.name, dog.age)
    dog.eat()
    if (dog.isInstanceOf[Dog]) {
      val d = dog.asInstanceOf[Dog]
      d.lookHome()
    } else {
      println("您传入的不是狗类!")
    }
  }
}

trait

作用:

​​​​​​​有些时候, 我们会遇到一些特定的需求, 即: 在不影响当前继承体系的情况下, 对某些类(或者某些对象)的功能进行加强, 例如: 有猴子类和大象类, 它们都有姓名, 年龄, 以及吃的功能, 但是部分的猴子经过马戏团的训练后, 学会了骑独轮车. 那骑独轮车这个功能就不能定义到父类(动物类)或者猴子类中, 而是应该定义到特质中. 而Scala中的特质, 要用关键字trait修饰.

特点:

  • 特质可以提高代码的复用性.

  • 特质可以提高代码的扩展性和可维护性.

  • 类与特质之间是继承关系, 只不过类与类之间只支持单继承, 但是类与特质之间, 既可以单继承, 也可以多继承.

  • Scala的特质中可以有普通字段, 抽象字段, 普通方法, 抽象方法.

注意:

  1. 如果特质中只有抽象内容, 这样的特质叫: 瘦接口.

  2. 如果特质中既有抽象内容, 又有具体内容, 这样的特质叫: 富接口.

格式: 

trait 特质名称 {
    // 普通字段
    // 抽象字段
    
    // 普通方法
    // 抽象方法
}

 继承特质 :

class 类 extends 特质1 with 特质2 {
    // 重写抽象字段
    // 重写抽象方法
}

 

注意

  • scala中不管是类还是特质, 继承关系用的都是extends关键字

  • 如果要继承多个特质(trait),则特质名之间使用with关键字隔开

代码示例: (类继承单个特质

  1. 创建一个Logger特质,添加log(msg:String)方法

  2. 创建一个ConsoleLogger类,继承Logger特质,实现log方法,打印消息

  3. 添加main方法,创建ConsoleLogger对象,调用log方法.

package test7

object Test1 {
  trait Logger {
    def log(msg: String)
  }

  class ConsoleLogger extends Logger {
    override def log(msg: String): Unit = println(msg)
  }

  def main(args: Array[String]): Unit = {
    val consoleLogger = new ConsoleLogger
    consoleLogger.log("trait入门:类继承单个特质")
  }
}

 

代码示例:(类继承多个trait

  1. 创建一个MessageSender特质,添加send(msg:String)方法

  2. 创建一个MessageReceiver特质,添加receive()方法

  3. 创建一个MessageWorker类, 继承这两个特质, 重写上述的两个方法

  4. 在main中测试,分别调用send方法、receive方法

package test7

object Test2 {
  trait MessageSender {
    def send(msg: String)
  }

  trait MessageReceiver {
    def receive()
  }

  class MessageWorker extends MessageSender with MessageReceiver {
    override def send(msg: String): Unit = println("发送消息:" + msg)

    override def receive(): Unit = println("消息已经收到!")
  }

  def main(args: Array[String]): Unit = {
    val messageWorker = new MessageWorker
    messageWorker.send("hello,你好!")
    messageWorker.receive()
  }
}

 

代码示例:(object继承trait

  1. 创建一个Logger特质,添加log(msg:String)方法

  2. 创建一个Warning特质, 添加warn(msg:String)方法

  3. 创建一个单例对象ConsoleLogger,继承Logger和Warning特质, 重写特质中的抽象方法

  4. 编写main方法,调用单例对象ConsoleLogger的log和warn方法

package test7

object Test3 {
  trait Logger {
    def log(msg: String)
  }

  trait Warning {
    def warn(msg: String)
  }

  object ConsoleLogger extends Logger with Warning {
    override def log(msg: String): Unit = println("控制台日志信息:" + msg)

    override def warn(msg: String): Unit = println("控制台警告信息:" + msg)
  }

  def main(args: Array[String]): Unit = {
    ConsoleLogger.log("我是一条普通日志信息!")
    ConsoleLogger.warn("我是一条警告日志信息!")
  }
}

代码示例:(演示trait中的成员)

  1. 定义一个特质Hero, 添加具体字段name(姓名), 抽象字段arms(武器), 具体方法eat(), 抽象方法toWar()

  2. 定义一个类Generals, 继承Hero特质, 重写其中所有的抽象成员.

  3. 在main方法中, 创建Generals类的对象, 调用其中的成员.

package test7

object Test4 {
  trait Hero {
    //具体字段
    var name = ""
    //抽象字段
    var arms: String

    //具体方法
    def eat() = println("喝酒吃肉,养精蓄锐!")

    //抽象方法
    def toWar()
  }

  class Generals extends Hero {
    override var arms: String = ""

    override def toWar(): Unit = println(s"${name}带着${arms},上阵杀敌!")
  }

  def main(args: Array[String]): Unit = {
    val generals = new Generals
    generals.name = "关羽"
    generals.arms = "青龙偃月刀"
    println(generals.name, generals.arms)
    generals.eat()
    generals.toWar()
  }
}

样例类

在Scala中, 样例类是一种特殊类,一般是用于保存数据的(类似于Java POJO类), 在并发编程以及Spark、Flink这些框架中都会经常使用它。

格式:

case class 样例类名([var/val] 成员变量名1:类型1, 成员变量名2:类型2, 成员变量名3:类型3){}

  • 如果不写, 则变量的默认修饰符是val, 即: val是可以省略不写的.

  • 如果要实现某个成员变量值可以被修改,则需手动添加var来修饰此变量.

代码示例:

package test7

object Test5 {
  case class Person(var name: String = "张三", var age: Int = 23) {}

  def main(args: Array[String]): Unit = {
    var person = new Person()
    println(person)

    person.age = 56
    println(person)
  }
}

样例类中的默认方法

  • apply()方法

  • toString()方法

  • equals()方法

  • hashCode()方法

  • copy()方法

  • unapply()方法

功能详解:

  • apply()方法

    • 可以让我们快速地使用类名来创建对象, 省去了new这个关键字

    • 例如: val p = Person()

  • toString()方法

    • 可以让我们通过输出语句打印对象时, 直接打印该对象的各个属性值.

    • 例如: println(p) 打印的是对象p的各个属性值, 而不是它的地址值

  • equals()方法

    • 可以让我们直接使用==来比较两个样例类对象的所有成员变量值是否相等.

    • 例如: p1 == p2 比较的是两个对象的各个属性值是否相等, 而不是比较地址值

  • hashCode()方法​​​​​​​
    • 用来获取对象的哈希值的. 即: 同一对象哈希值肯定相同, 不同对象哈希值一般不同.
    • 例如:

         

 

copy()方法

  • 可以用来快速创建一个属性值相同的实例对象,还可以使用带名参数的形式给指定的成员变量赋值.

  • 例如:

        

 

unapply()方法

  • 一般用作提取器

 代码示例:

package test7

object Test6 {
  case class Person(var name: String, var age: Int) {}

  def main(args: Array[String]): Unit = {
    val person1 = Person("张三", 23)
    println(person1)

    val person2 = Person("张三", age = 23)
    println(person2)

    println(person1 == person2)
    println(person1.hashCode())
    println(person2.hashCode())

    val person3 = person2.copy(age = 50)
    println(person3)
  }
}

样例对象:

​​​​​​​在Scala中, 用case修饰的单例对象就叫: 样例对象, 而且它没有主构造器 , 它主要用在两个地方:

  1. 当做枚举值使用.

    枚举: 就是一些固定值, 用来统一项目规范的.

  2. 作为没有任何参数的消息传递

    注意: 这点目前先了解即可, 后续讲解Akka并发编程时会详细讲解.

case object 样例对象名

 代码示例:

  • 定义特质Sex, 表示性别, 且它只有两个实例(Male: 表示男, Female: 表示女)

  • 定义Person类,它有两个成员变量(姓名、性别)

  • 在测试类中创建Person类的对象, 并测试.

package test7

object Test7 {
  trait Sex

  case object Male extends Sex

  case object Female extends Sex

  case class Person(name: String, sex: Sex) {}

  def main(args: Array[String]): Unit = {
    val person = Person("张三", Male)
    var person1 = Person("李四", Female)
    println(person)
    println(person1)
  }
}

 代码示例:(计算器)​​​​​​​

  • 定义样例类Calculate, 并在其中添加4个方法, 分别用来计算两个整数的加减乘除操作.

  • 在main方法中进行测试.

package test7

object Test8 {
  case class Calculate(a: Int, b: Int) {
    def add() = a + b

    def sub() = a - b

    def mul() = a * b

    def div() = a / b
  }

  def main(args: Array[String]): Unit = {
    val calculate = Calculate(10, 3)
    println("加法" + calculate.add())
    println("减法" + calculate.sub())
    println("乘法" + calculate.mul())
    println("除法" + calculate.div())
  }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿瞒有我良计15

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值