前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >scala(十二) 特质

scala(十二) 特质

作者头像
用户1483438
发布2022-04-18 15:09:24
5030
发布2022-04-18 15:09:24
举报
文章被收录于专栏:大数据共享大数据共享

特质的定义

Scala语言中,采用特质(trait)来代替接口的概念,也就是说,多个类具有相同的特质(trait)时,就可以将这个特质(trait)独立出来,采用关键字trait声明。

Scala中的trait中即可以有抽象属性和方法,也可以有具体的属性和方法,一个类可以混入(mixin)多个特质

Scala引入trait特征,第一可以替代Java的接口,第二个也是对单继承机制的一种补充。

特质的语法

trait 特质名 { trait体 }

案例一:人类有很多不同的特性,有的人会唱歌,有的人会跳舞,也有即会唱歌又会跳舞。

代码语言:javascript
复制
  // 跳舞
  trait Dancing{
    //具体的舞蹈有子类去实现
    def dance():Unit
  }

  // 唱歌
  trait Sing{
    //具体的歌曲有子类去实现
    def song():Unit
  }

若是把你我他分类的话,那我们都属于人类

代码语言:javascript
复制
  // 人类
  abstract class Person{

    val name:String

  }

创建学生类;每个学生都会一些基本舞蹈和歌曲,就需要使用 with 去实现 DancingSing两个 特质(trait)

代码语言:javascript
复制
  //学生
  class Student(val na:String) extends Person with Dancing with Sing {
    //重写 姓名属性
    override val name:String=this.na

    override def dance(): Unit = {
      println(s"$name 在跳,广播体操")
    }

    override def song(): Unit = {
      println(s"$name 在唱,保卫黄河")
    }
  
}
代码语言:javascript
复制
  def main(args: Array[String]): Unit = {
    val student=new Student("马小跳")
    student.dance() // 马小跳 在跳,广播体操
    student.song() // 马小跳 在唱,保卫黄河
  }

子类需要继承父类的时候,此时extends 关键字用于继承class,特质的实现通过with关键字实现。

案例二:程序员工作离不开电脑,一台电脑组成又分为很多模块。 主板:

代码语言:javascript
复制
  trait Motherboard{
    def boardInfo():Unit 
  }

CPU:

代码语言:javascript
复制
  trait CPU{
    def cpuInfo():Unit
  }

内存:

代码语言:javascript
复制
  trait RAM{
    def ramInfo():Unit
  }

额太多了,就不写了,

定义好电脑的模板

代码语言:javascript
复制
  class Computer(val board:String,val cpu:String,val ram:String) extends Motherboard with CPU with RAM{
    override def boardInfo(): Unit = {
      println(s"主板:$board")
    }

    override def cpuInfo(): Unit = println(s"CPU:$cpu")

    override def ramInfo(): Unit = println(s"内存:$ram")
  }

根据我们的配置,组装电脑

代码语言:javascript
复制
  def main(args: Array[String]): Unit = {

    val   computer=new Computer("超级好的主板","超级好的CPU","超级大的内存")
    
    computer.boardInfo()
    computer.cpuInfo()
    computer.ramInfo()
   
  }
代码语言:javascript
复制
主板:超级好的主板
CPU:超级好的CPU
内存:超级大的内存

子类不需要继承父class 的时候 ,此时 第一个特质的实现通过 extends 关键字来实现,其他特质依旧使用 with关键字。

通过上面两种案例讲解说明,特质(trait) 的两种实现方式

  1. 子类需要继承父类的时候,此时extends 关键字用于继承class,特质的实现通过with关键字实现。
  2. 子类不需要继承父class 的时候 ,此时 第一个特质的实现通过 extends 关键字来实现,其他特质依旧使用 with关键字。

在特质(trait)中 既可以定义抽象方法,也可以定义具体方法。 如:拿案例二演示(不管合不合理,意思明白就行)。

代码语言:javascript
复制
  trait Motherboard{

    def boardInfo():Unit

    //内存插口
    def ramInterface(): Unit ={
      println("只有两个内存插口")
    }

  }

具体方法,子类可不用重写,直接调用即可。

代码语言:javascript
复制
  def main(args: Array[String]): Unit = {
    val computer=new Computer("超级好的主板","超级好的CPU","超级大的内存")
    computer.ramInterface()
  }

在特质(trait)中 既可以定义抽象属性,也可以定义具体属性。

代码语言:javascript
复制
  // 主板
  trait Motherboard{
    // 抽象属性
    val info:String
    // 具体属性
    val area=18
    

    def boardInfo():Unit

    //内存插口
    def ramInterface(): Unit ={
      println("只有两个内存插口")
    }

  }

抽象属性需要被子类重写

代码语言:javascript
复制
  class Computer(val board:String,val cpu:String,val ram:String) extends Motherboard with CPU with RAM{
    override def boardInfo(): Unit = println(s"主板:$board")
    override def cpuInfo(): Unit = println(s"CPU:$cpu")
    override def ramInfo(): Unit = println(s"内存:$ram")

    // 重写 Motherboard 中的 info 属性
    override val info: String = "主板"
  }

基本语法:

  1. 没有父类:class 类名 extends 特质1 with 特质2 with 特质3 …
  2. 有父类:class 类名 extends 父类 with 特质1 with 特质2 with 特质3…

说明

  1. 类和特质的关系:使用继承的关系。
  2. 当一个类去继承特质时,第一个连接词是extends,后面是with。
  3. 如果一个类在继承特质和父类时,应当把父类写在extends后。

对象的混入

这里用上面的案例一 演示说明; 现在的学生,若只会 唱歌跳舞 肯定是不行的,有点家庭比较好的学生肯定还有其他的,。比如:弹钢琴,弹吉他

乐器

代码语言:javascript
复制
  trait MusicalInstruments{
    def play:Unit
  }

由于该特质(trait)属于个别同学所独有的,所以就无需定义到 Student 中。而是让 使用with关键字,让个别对象去实现。

代码语言:javascript
复制
  def main(args: Array[String]): Unit = {
    val weh=new Student("王二虎") with  MusicalInstruments {
      override def play(): Unit = {println(s"$name 弹棉花")}
    }
    weh.dance()
    weh.song()
    weh.play()

    val lff=new Student("李菲菲") with  MusicalInstruments {
      override def play(): Unit = {println(s"$name 弹棉花")}
    }

    lff.dance()
    lff.song()
    lff.play()

    val mxt=new Student("马小跳")

    mxt.dance()
    mxt.song()

  }

输出结果

代码语言:javascript
复制
王二虎 在跳,广播体操
王二虎 在唱,保卫黄河
王二虎 弹棉花
李菲菲 在跳,广播体操
李菲菲 在唱,保卫黄河
李菲菲 弹棉花
马小跳 在跳,广播体操
马小跳 在唱,保卫黄河

这种行为就叫对象混入 (2)一个类可以混入(mixin)多个特质 (3)所有的Java接口都可以当做Scala特质使用 (4)动态混入:可灵活的扩展类的功能

方法叠加

由于一个类可以混入(mixin)多个trait,且trait中可以有具体的属性和方法,若混入的特质中具有相同的方法(方法名,参数列表,返回值均相同),必然会出现继承冲突问题。冲突分为以下两种:

第一种,一个类(Sub)混入的两个trait(TraitA,TraitB)中具有相同的具体方法,且两个trait之间没有任何关系,解决这类冲突问题,直接在类(Sub)中重写冲突方法。

第二种,一个类(Sub)混入的两个trait(TraitA,TraitB)中具有相同的具体方法,且两个trait继承自相同的trait(TraitC),及所谓的“钻石问题”,解决这类冲突问题,Scala采用了特质叠加的策略。

所谓的特质叠加,就是将混入的多个trait中的冲突方法叠加起来;

定义五个特质(trait)

代码语言:javascript
复制
  trait A{
    def sayHello()={
      println("hello","A")
    }
  }

  trait B{

    def sayHello()={
      println("hello","B")
    }
  }

  trait C{

    def sayHello()={
      println("hello","C")
    }
  }

  trait D{

    def sayHello()={
      println("hello","D")
    }
 }

定义 一个 H 类 去实现这些特质(trait)

代码语言:javascript
复制
class H extends A with B with C with D{}

通过 H 调用 sayHello()

代码语言:javascript
复制
  def main(args: Array[String]): Unit = {
    val h =new H
    h.sayHello()

  }

结果报错了;意思是说,这些函数有歧义,不指定该调用谁的 sayHello 函数。

代码语言:javascript
复制
Error:(34, 9) class H inherits conflicting members:
  method sayHello in trait C of type ()Unit  and
  method sayHello in trait D of type ()Unit
(Note: this can be resolved by declaring an override in class H.)
  class H extends A with B with C with D{}

也提示你this can be resolved by declaring an override in class H. ;让你重写 sayHello()

代码语言:javascript
复制
  class H extends A with B with C with D{

    override def sayHello(): Unit =  println("hello","H")
  }

这样运行就没有问题了

代码语言:javascript
复制
(hello,H)

还有一种情况,子类中可以通过super 访问父类的方法

代码语言:javascript
复制
  class H extends A with B with C with D{
    super.sayHello()
    override def sayHello(): Unit =  println("hello","H")
  }

结果:super.sayHello()打印的是 D 的 sayHello()。

代码语言:javascript
复制
(hello,D)
(hello,H)

这种结果产生的原因很简单,因为的继承顺序是从 A-D,所以从左到右的顺序去找最后一个重名的函数,所以运行的是特质D的sayHello()。

若要调用指定的特质的sayHello(),可以使用[]指定。 如调用 特质B 的sayHello()。 语法:

super[特质名].方法。

代码语言:javascript
复制
 class H extends A with B with C with D{
    super[B].sayHello()
    override def sayHello(): Unit =  println("hello","H")
  }
代码语言:javascript
复制
(hello,B)
(hello,H)

以上就是第一种冲突方式,及解决方式。

说完第一种,还有第二种,指多个特质(trait)是有联系的。 再定义一个特质(trait) F 并定义一个 sayHello()

代码语言:javascript
复制
  trait F{
    def sayHello()={
      println("hello","F")
    }
  }

其他特质A-D 继承 F 重写 sayHello(),并各自调用父类(F) 的sayHello()。

代码语言:javascript
复制
  trait A extends F {

    override def sayHello()={
      println("hello","A")
      super.sayHello()
    }
  }

  trait B extends F{

    override def sayHello()={
      println("hello","B")
      super.sayHello()
    }
  }

  trait C extends F{

    override def sayHello()={
      println("hello","C")
      super.sayHello()
    }
  }

  trait D extends F{

    override def sayHello()={
      println("hello","D")
      super.sayHello()
    }
  }

H 不变

代码语言:javascript
复制
  class H extends A with B with C with D{
    super.sayHello()
    override def sayHello(): Unit =  println("hello","H")
  }

此时的运行结果如何?

代码语言:javascript
复制
  def main(args: Array[String]): Unit = {
    val h =new H
    h.sayHello()
  }

结果:这种就是所谓的“钻石问题”

代码语言:javascript
复制
(hello,D)
(hello,C)
(hello,B)
(hello,A)
(hello,F)
(hello,H)

原理也很简单。 首先运行的是H,H调用spuer 会运行特质D,然后由D调用 super ,此时D并不是调用 F,而是指向的是上一个特质C ,然后C又指向B,B又指向A,此时A才真正的指向F。由于A-D super 是在打印下面所有先这执行的打印后执行的super。H和其他相反,super在前,打印在后。运行最后运行的是H。

钻石的形式
钻石的形式

红色:表示继承或实现 黑色:表示指向

自身类型

有这么一个需求,需要将对象持久化(保存到磁盘) 回顾 java 实现对象持久化步骤

  1. 实现 Serializable 接口
  2. 提供get/set 方法
  3. 序列化,使用ObjectOutputStream 对对象写入文件
  4. 反序列化,读取文件生成对象,使用ObjectInputStream

scala 中也是也是如此 创建一个Person类,提供get/set 方法,暂时不指定 Serializable 接口。

代码语言:javascript
复制
  class Person {
    // id
    @BeanProperty var id:Int=_
    // 姓名
    @BeanProperty var name:String=_
    // 年龄
    @BeanProperty var age:Int=_
  }

序列化、反序列化是一个完整的功能,我们可以将其封装到单独的类中。

代码语言:javascript
复制
  class ObjectWriteAndRead{
    /**
     * 序列化
     * @param path 文件地址
     */
    def write(path:String): Unit ={
      try {
        val fos=new FileOutputStream(path)
        val objectInput=new ObjectOutputStream(fos)
        // 写入磁盘
       objectStream.writeObject(this)

        // 刷新与关闭
        objectStream.flush()
        objectStream.close()
        fos.close()
        println("序列化成功")
      }catch {
        case e:IOException=>{
          println("序列化失败",e)
        }
      }
    }
  }

Person 若要进行序列化或反序列化可以继承该类

代码语言:javascript
复制
 class Person  extends ObjectWriteAndRead{...}

若对象没有实现序列化接口,运行时肯定会报错

代码语言:javascript
复制
 def main(args: Array[String]): Unit = {
    //创建对象
    val person=new Person()
    person.setId(1001)
    person.setName("王小二")
    person.setAge(19)

    person.write("D:\\aaa.txt")
  }

报错

代码语言:javascript
复制
(序列化失败,java.io.NotSerializableException: com.admin.xxx.traita.Demo03$Person)

别觉得我说的是废话,遇到这种情况,我们很多时候都会忘记(是否实现了Serializable接口),只有等到运行报错时,翻看异常信息才恍然大悟。有没有一种机制能让我们在一开始就提示我们,而不是等到出问题之后?

这就需要自身类型了,它主要用于提醒子类,子类继承父类,需要满足继承父类的某些条件。如:必须实现 Serializable接口

语法:

this:类型 =>

案例:指定 ObjectWriteAndRead 的自身类型

代码语言:javascript
复制
  class ObjectWriteAndRead{
    this: Serializable =>
    ...
  }

此时 Person 类就弹错误,告知其需要实现 Serializable 接口。

自身类型
自身类型

按照提示实现 Serializable 接口。

代码语言:javascript
复制
class Person  extends ObjectWriteAndRead with Serializable {...}

然后再运行

代码语言:javascript
复制
序列化成功

这就是自身类型的作用,说重要也不重要,但是是一个很好的辅助,大大提高我们开发效率,毕竟减少了解决报错的时间。

最后也把反序列化完成吧,在ObjectWriteAndRead新增一个read函数,用于进行反序列化

代码语言:javascript
复制
 class ObjectWriteAndRead{
    this: Serializable =>
    /**
     * 序列化
     * @param path 文件地址
     */
    def write(path:String): Unit ={
      try {
        val fos=new FileOutputStream(path)
        val objectStream=new ObjectOutputStream(fos)
        // 写入磁盘
        objectStream.writeObject(this)

        // 刷新与关闭
        objectStream.flush()
        objectStream.close()
        fos.close()
        println("序列化成功")
      }catch {
        case e:IOException=>{
          println("序列化失败",e)
        }
      }
    }

    /**
     * 反序列化
     * @param path
     * @return
     */
    def read(path:String):AnyRef={

     try {
       val fis=new FileInputStream(path)

       val objectStream=new ObjectInputStream(fis)
       println("反序列化成功")
       objectStream.readObject()
     }catch {
       case e:IOException =>{
         println("反序列化失败",e)
         null
       }
     }
    }
  }

运行

代码语言:javascript
复制
  def main(args: Array[String]): Unit = {
    //创建对象
    val person=new Person()
    person.setId(1001)
    person.setName("王小二")
    person.setAge(19)
    // 序列化
    person.write("D:\\aaa.txt")

    //反序列化
    val newPerson = person.read("D:\\aaa.txt").asInstanceOf[Person]
    println(person.getId)
    println(person.getName)
    println(person.getAge)
  }

person.read("D:\aaa.txt") : 是一个反序列化的结果,类型是AnyRef .asInstanceOf[T] 用于类型强转

输出结果

代码语言:javascript
复制
序列化成功
反序列化成功
1001
王小二
19

完整代码

代码语言:javascript
复制
class Person  extends ObjectWriteAndRead with Serializable {
    // id
    @BeanProperty var id:Int=_
    // 姓名
    @BeanProperty var name:String=_
    // 年龄
    @BeanProperty var age:Int=_

  }

  def main(args: Array[String]): Unit = {
    //创建对象
    val person=new Person()
    person.setId(1001)
    person.setName("王小二")
    person.setAge(19)
    // 序列化
    person.write("D:\\aaa.txt")



    //反序列化
    val newPerson = person.read("D:\\aaa.txt").asInstanceOf[Person]
    println(person.getId)
    println(person.getName)
    println(person.getAge)

  }


  class ObjectWriteAndRead{
    this: Serializable =>
    /**
     * 序列化
     * @param path 文件地址
     */
    def write(path:String): Unit ={
      try {
        val fos=new FileOutputStream(path)
        val objectStream=new ObjectOutputStream(fos)
        // 写入磁盘
        objectStream.writeObject(this)

        // 刷新与关闭
        objectStream.flush()
        objectStream.close()
        fos.close()
        println("序列化成功")
      }catch {
        case e:IOException=>{
          println("序列化失败",e)
        }
      }
    }

    /**
     * 反序列化
     * @param path
     * @return
     */
    def read(path:String):AnyRef={

     try {
       val fis=new FileInputStream(path)

       val objectStream=new ObjectInputStream(fis)
       println("反序列化成功")
       objectStream.readObject()
     }catch {
       case e:IOException =>{
         println("反序列化失败",e)
         null
       }
     }
    }
  }

扩展

类型检查和转换

定义三个类

代码语言:javascript
复制
  class Person{
    val name="Person"
  }

  class Student extends Person{
    override val name: String = "Student"
  }

  class Teacher extends Person{
    override val name: String = "Teacher"
  }

Student 与 Teacher 都继承 Person

  1. obj.isInstanceOf[T]:判断obj是不是T类型。
代码语言:javascript
复制
  def main(args: Array[String]): Unit = {
    val stu:Person=new Student

    println(s"判断是否为 Student 类:${stu.isInstanceOf[Student]}")
    println(s"判断是否为 Teacher 类:${stu.isInstanceOf[Teacher]}")
  }
代码语言:javascript
复制
判断是否为 Student 类:true
判断是否为 Teacher 类:false
  1. obj.asInstanceOf[T]:将obj强转成T类型。
代码语言:javascript
复制
  def main(args: Array[String]): Unit = {
    val stu:Person=new Student

    val o1=stu.asInstanceOf[Person]
    println("o1:"+o1.name)

    val o2=stu.asInstanceOf[Student]
    println("o2:"+o2.name)

    val o3=stu.asInstanceOf[Teacher]
    println("o3:"+o3.name)

  }

o3 无法转换 无法转换为 Teacher

代码语言:javascript
复制
o1:Student
o2:Student
Exception in thread "main" java.lang.ClassCastException: com.admin.xxx.traita.Demo04$Student cannot be cast to com.admin.xxx.traita.Demo04$Teacher

至于为啥o1 输出的也是 Student 因为Scala 中属性也具有多态性。

  1. classOf获取对象的类名。
代码语言:javascript
复制
  def main(args: Array[String]): Unit = {

    val clazz: Class[Student] = classOf[Student]
    println(clazz.getSimpleName) // 获取类名
  }
代码语言:javascript
复制
Student

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 特质的定义
    • 特质的语法
    • 对象的混入
    • 方法叠加
      • 以上就是第一种冲突方式,及解决方式。
      • 自身类型
      • 扩展
        • 类型检查和转换
        相关产品与服务
        文件存储
        文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档