zoukankan      html  css  js  c++  java
  • 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继承自某个超类,伴生类不会继承改超类,该超类会被任何实现该特质的类继承。

    用放荡不羁的心态过随遇而安的生活
  • 相关阅读:
    volley框架使用
    Insert Interval
    candy(贪心)
    Best Time to Buy and Sell Stock
    Best Time to Buy and Sell Stock III
    distinct subsequences
    edit distance(编辑距离,两个字符串之间相似性的问题)
    trapping rain water
    word break II(单词切分)
    sudoku solver(数独)
  • 原文地址:https://www.cnblogs.com/re-myself/p/5532484.html
Copyright © 2011-2022 走看看