• scala学习之特质(trait)


    特质,很像java中的接口,但是又有些不同,比如实现方法,当然java8也可以在接口中实现一个方法了,但是只能定义一个default方法。
    当做接口使用

    //特质
    trait Logger {
       def log(msg:String)
    }
    
    trait ConsoleLogger extends Logger{  //extends not implements
       def log(msg: String){println(msg)} //不需要写 Overrride
      //在重写 特质的抽象方法是不需要写 Overrride关键字
      //如果需要的特质不止一个,可以使用with连接
    
      //特质中的方法并不一定是抽象的
      def write(msg:String){println(msg)}
    }

    含有具体实现
    例子来源于 快学scala
    先定义这么一个trait

    trait Logged {
      def log(msg : String){}
    }
    

    写一个class继承它

    class SavingAccount extends Account with Logged{
      def withdraw(ammount :Double): Unit ={
        if (ammount > balance) log("this is a test")
        else  balance -= ammount
      }
    
    }

    从代码上看,在Logged 中,我们并没有实现此方法,所以这里的log是无意义的。
    但是在scala中并非如此。

    trait ConsoleLogged extends Logged{
       override def log(msg:String){println(msg)}
    }

    定义一trait继承Logged,并实现log方法,然后锣鼓一响,好戏开场

    object TestTrait extends App{
      val acct = new SavingAccount with ConsoleLogged
      acct.withdraw(2.2)
    }
    

    你会控制台有日志打印。
    下面看下多个trait的执行顺序,是一件非常有意思的事情,首先写几个trait,代码如下:

    trait ConsoleLogged extends Logged{
       override def log(msg:String){println("console=>"+msg )}
    }
    trait TimeStampLogger extends Logged{
      override def log(msg:String){println(msg+".print at."+new Date())}
    }
    
    trait ShortLogger extends Logged{
      val maxLength = 15
      override def log(msg:String): Unit ={
        super.log(
          if (msg.length<=maxLength) msg else msg.substring(0,maxLength)+"...short"
        )
      }
    }

    测试:

      val acct = new SavingAccount with ConsoleLogged
      acct.withdraw(3.2)
    
      val acct1 = new SavingAccount with ConsoleLogged with TimeStampLogger with ShortLogger
      acct1.withdraw(2.2)
    
      val acct2=new SavingAccount with TimeStampLogger with ConsoleLogged with  ShortLogger
      acct2.withdraw(2.5)
    
      val acct3=new SavingAccount with ConsoleLogged with ShortLogger with  TimeStampLogger
      acct3.withdraw(2.5)
    
      val acct4=new SavingAccount with TimeStampLogger with ShortLogger with  ConsoleLogged
      acct4.withdraw(2.5)

    输出如下:

    console=>SavingAccount print =>
    SavingAccount p...short.print at.Thu Aug 13 17:54:17 CST 2015
    console=>SavingAccount p...short
    SavingAccount print =>.print at.Thu Aug 13 17:54:17 CST 2015
    console=>SavingAccount print =>

    从打印结果分析看,如果trait是从左到右开始执行,那么第三条和第五条没有输出是无法解释的;那么如果从右开始执行呢,可以看到第二条输出,先执行ShortLogger,调用super.log,执行TimeStampLogger,那么ConsoleLogged似乎没有被调用;以此解释第三条输出也是说的通的,而TimeStampLogger没有被执行,那么第四条数据呢,只是执行了TimeStampLogger,同理第五条也是如此,如果没有super,似乎前面的trait中的方法不会被调用,但是,如果我在TimeStampLogger如此做的话,需要打上abstract标签,输出结果和上面相同,这里有些疑问,暂且存疑。
    在特质中声明字段
    在trait中字段声明可以是具体的,也可以是抽象的,如果在字段声明时初始化,就是具体的。如果有子类继承吃trait,改字段在JVM中,实际上是属于子类的字段,和子类字段放在一起。
    声明抽象字段

    trait ShortLogger extends Logged{
      val maxLength : Int
      override def log(msg:String): Unit ={
        super.log(
          if (msg.length<=maxLength) msg else msg.substring(0,maxLength)+"...short"
        )
      }
    }

    需要在子类中对其初始化,例如:

    class SavingAccount extends Account  with ShortLogger{
      def withdraw(ammount :Double): Unit ={
        if (ammount > balance) log("SavingAccount print =>")
        else  balance -= ammount
      }
    
      override val maxLength: Int = 15
    }

    也可以如此:

      val acct1 = new SavingAccount with ConsoleLogged with TimeStampLogger with ShortLogger{
        val maxLength = 15
      }

    特质的构造顺序
    trait是有构造顺序的,初始化一个类,构造顺序如下:

    1. 先调用超类的构造方法
    2. trait构造方法在超类构造方法之后和类的构造方法之前执行
    3. trait由左到右被构造(待验证)
    4. 在trait中,父trait首先被构造
    5. 如果多个trait共有一个父trait,而父trait已经被构造,则不会父trait不会再被构造
    6. 所有的trait被构造完毕,类被构造
    new SavingAccount with ConsoleLogged  with TimeStampLogger with ShortLogger

    根据《快学scala》中介绍的构造顺数,线性化相反的方向,串接并去掉重复选项,右侧胜出
    以上面的为例子:
    SavingAccount >>ShortLogger>>TimeStampLogger >>ConsoleLogged >>Logged>>Account
    但是输出结果:

    SavingAccount p...short.print at.Fri Aug 14 17:17:41 CST 2015

    但是从输出结果上分析,少了ConsoleLogged 的输出结果,至于为什么,恕在下才疏学浅,还没有找到结果。

    scala执行时需要JVM的,但是JVM只支持单一继承。那么scala的trait在JVM中是怎么被翻译的呢?

    如果scala只有抽象方法时,trait被翻译成接口,如果trait有具体的方法实现,scala会创建一个伴生类,该伴生类用静态方法存放特质的方法,这些伴生类中不会有任何字段。特质中的字段会对应到接口中抽象的setter和getter方法。如果trait继承自某个超类,伴生类不会继承改超类,该超类会被任何实现该特质的类继承。

    用放荡不羁的心态过随遇而安的生活
  • 相关阅读:
    决策树和随机森林
    6个开源数据科学项目
    机器学习:梯度下降
    Python中的数据结构
    方差分析介绍(结合COVID-19案例)
    html5
    归并排序
    前端与后端
    Dw3 Sublime text 3 UltraEdit XAMMPP 火狐浏览器 ISS
    ECMAScript JavaScript JScript
  • 原文地址:https://www.cnblogs.com/re-myself/p/5532484.html
Copyright © 2020-2023  润新知