zoukankan      html  css  js  c++  java
  • 快学Scala(10)--特质

    当做接口使用的特质:

    trait Logger {
      def log(msg:String) //抽象方法
    }
    
    
    class ConsoleLogger extends Logger with Cloneable with Serializable{
     def log(msg: String): Unit = {println(msg)}
    }
    

      注:1. 在重写特质的抽象方法时不需要给出override关键字;

        2. 如果需要的特质不止一个,可以使用with关键字来添加额外的特质

    带有具体实现的特质:

    trait Logger {
      def log(msg:String) //抽象方法
    }
    
    
    trait ConsoleLogger{
      def log(msg: String): Unit = {println(msg)}
    }
    
    
    class SavingAccounts extends Logger with ConsoleLogger{
      var balance = 0: Double
    
      def withdraw(amount: Double): Unit = {
        if(amount > balance) log("Insufficient funds")
        else balance -= amount
      }
    }
    

      在这个例子中,SavingsAccount从ConsoleLogger特质得到了一个具体的log方法实现。用JAVA接口的话,这是不可能的。我们说ConsoleLogger的功能被“混入”了SavingsAccount类。

      注:但是让特质拥有具体行为存在一个弊端。当特质改变时,所有混入了该特质的类都必须重新编译。

    带有特质的对象

    可以在构建单个对象时添加特质,表示在构造对象的时候“混入”了更好的类,在这个时候优先执行构建对象时添加的特质内的方法。

    叠加在一起的特质

    可以为类或对象添加多个相互调用的特质,从最后一个开始。

    trait Logger {
      def log(msg:String) { }
    }
    
    trait ConsoleLogger extends Logger{
      override def log(msg: String): Unit = {println(msg)}
    }
    
    class SavingAccounts extends Logger with ConsoleLogger{
    
      var balance = 0: Double
    
    
      def withdraw(amount: Double): Unit = {
        if(amount > balance) log("Insufficient funds")
        else balance -= amount
      }
    }
    
    trait TimestampLogger extends Logger{
      override def log(msg: String): Unit = {
        super.log(new java.util.Date() + " " + msg)
      }
    }
    
    trait ShortLogger extends Logger{
      val maxLength = 15
    
      override def log(msg: String): Unit = {
        super.log(if(msg.length <= maxLength)msg else msg.substring(0, maxLength-3) + "...")
      }
    }
    
    object TestTrait {
    
      def main(args: Array[String]): Unit = {
        val acct1 = new SavingAccounts with ConsoleLogger with TimestampLogger with ShortLogger
        val acct2 = new SavingAccounts with ConsoleLogger with ShortLogger with TimestampLogger
    
        acct1.withdraw(1.0)
        acct2.withdraw(1.0)
      }
    
    
    }
    

      执行TestTrait对象的main方法结果如下:

    acct1首先执行ShortLogger的log方法,然后用super.log调用TimestampLogger

    acct2正好相反

    如果需要控制具体是哪一个特质的方法被调用,则可以在方括号中给出名称:super[ConsoleLogger].log(...)。这里给出的类型必须是直接超类型;你无法使用继承层级中更远的特质或类。

    当做富接口使用的特质

    trait Logger {
      def log(msg:String)
      def info(msg: String) {log("INFO: " + msg)}
      def warn(msg: String) {log("WARN: " + msg)}
      def severe(msg: String) {log("SEVERE: " + msg)}
    }
    
    class SavingAccounts extends Logger{
    
      var balance = 0: Double
    
    
      def withdraw(amount: Double): Unit = {
        if(amount > balance) severe("Insufficient funds")
        else balance -= amount
      }
    
      override def log(msg: String): Unit = {println(msg)}
    }
    

      

    特质中的具体字段

    特质中的字段可以是具体的,也可以是抽象的。如果给出了初始值,那么字段就是具体的。

    来自特质的字段被加入子类字段。

    特质中的抽象字段

    特质中未被初始化的字段在具体的子类中必须被重写

    特质构造顺序

    构造器将按照如下的顺序执行:

    1. 首先调用超类的构造器
    2. 特质构造器在超类构造器之后、类构造器之前执行
    3. 特质由左到右被构造
    4. 每个特质当中,父特质优先被构造
    5. 如果多个特质共有一个父特质,而那个父特质已经被构造,则不会被再次构造
    6. 所有特质构造完毕,子类被构造

    举例来说,考虑如下一个类:

    class SavingsAccount extends Account with FileLogger with ShortLogger
    

      构造器将按照如下的顺序构造

    1. Account(超类)
    2. Logger(第一个特质的父特质)
    3. FileLogger(第一个特质)
    4. ShortLogger(第二个特质,注意此时它的父特质Logger已经被构造)
    5. SavingsAccount(类)

    初始化特质中的字段

    特质不能有构造器参数,这是特质与类的唯一技术差别

    在特质中放置一个抽象字段,在子类的构造函数中对这个抽象字段进行初始化是不可行的:

    trait FileLogger extends Logger{
    
      val filename: String
      val out = new PrintStream(filename)
    
      override def log(msg: String): Unit = {out.println(msg); out.flush()}
    }
    
    object TestTrait {
    
      def main(args: Array[String]): Unit = {
        val acct = new SavingAccounts with FileLogger {
          override val filename: String = "myapp.log"
        }
      }
    
    }
    

      在这种情况下,由于FileLogger优先于子类被构造(子类就是一个扩展自SavingAccounts,混入FileLogger的匿名类),故在对FileLogger的out字段初始化的时候会抛出空指针异常。

    有两种解决方法:

    1. 提前定义:能够解决问题,但不是很漂亮

    class SavingsAccount extends {
      val filename = "savings.log"
    } with Account with FileLogger {
    ...
    }  

      在FileLogger被构造的时候,filename已经是初始化过的了

    2. 懒值

    trait FileLogger extends Logger{
      val filename: String
      lazy val out = new PrintStream(filename)
      override def log(msg: String): Unit = {out.println(msg); out.flush()}
    }
    

      如此一来,out字段将在初次被使用时才会初始化。而在那个时候,filename字段应该已经被设好值了。不过,由于懒值在每次使用前都会检查是否已经初始化,它们用起来并不是那么高效。

  • 相关阅读:
    记录一段QQ关于 UNIGUI 的Session 时间设定
    uniGUI Cannot read property 'remove' of null
    基于kbmMW Configuration Framework 实现配置文件对象化
    每日日报42
    每日日报41
    每日日报40
    解决Ajax无法跳转到其他界面
    每日日报39
    每日日报38
    《软件项目成功之道》阅读笔记01
  • 原文地址:https://www.cnblogs.com/PaulingZhou/p/6657673.html
Copyright © 2011-2022 走看看