当前位置: 首页 > news >正文

Scala学习(十)特质

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

Scala提供“特质”而非接口。特质可以同时拥有抽象方法和具体方法,而类可以实现多个特质。

1.当作接口使用的特质

Scala特质完全可以像Java的接口那样工作。例如:

trait Logger{
    def log(msg: String)  //这个是抽象方法
}

注意,你不需要讲方法声明为abstract,特质中未被实现的方法默认就是抽象的。

子类可以给出实现:

class ConsoleLogger extends Logger{  //用extends,而不是implements
    def log(msg: String){ println(msg) }  //不需要写override
}

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

class ConsoleLogger extends Logger with Cloneable with Serializable

所有的Java接口都可以作为Scala特质使用,和Java一样,Scala类只能有一个超类,但可以有任意数量的特质。

2.带有具体实现的特质

在Scala中,特质中的方法并不需要一定是抽象的。举例来说,我们可以把我们的具体类变成一个特质:

trait Logger{  
    def log(msg: String){ println(msg) }  
}

如何使用:

class ConsoleLogger extends Logger{
    def write(msg: String){ log("Hello World")}  
}

3.带有特质的对象

首先,特质也是可以被奇特特质扩展的

trait Logger{  
    def log(msg: String){ log("Hello World") }  
}

trait ConsoleLogger extends Logger{
    override def log(msg: String){ println(“ConsoleLogger:” + msg) }  
}

trait FileLogger extends Logger{
    override def log(msg: String){ println(“FileLogger:” + msg) }  
}

你可以在构造对象的时候介入这个特质:

val acct = new SavingsAccount with ConsoleLogger 

当我们在acct对象上调用log方法时,ConsoleLogger特质的log方法就会被执行。当然了,另一个对象可以加入不同的特质:

val act = new new SavingsAccount with FileLogger 

4.叠加在一起的特质

//日志特质
trait Logger{
    def log(msg: String){} 
}

//打印日志
trait ConsoleLogger{ 
    def log(msg: String){ println(msg) }
}

//给日志添加时间戳
trait TimestampLogger extends Logged{
    override def log(msg: String){
        super.log(new java.util.Date() + " " + msg)
    }
}

//过长日志截断
trait ShortLogger extends Logged{
    val maxLength = 15
    override def log(msg: String){
        super.log(if(msg.length <= maxLength) msg else msg.substring(0, maxLength -3) + "...")
    }
}

//存款类
class SavingsAccount extends Logged{
    def withdraw(amount: Double){
        if(amount: Double) log("Insufficient funds")
        else 
        ...
    }
}

特质的调用,要根据特指添加的顺序界定。一般来说特质是从最后一个开始被处理,例如:

val acct1 = new SavingsAccount with ConsoleLogger with TimestampLogger with ShortLogger

val acct2 = new SavingsAccount with ConsoleLogger with ShortLogger with TimestampLogger

如果我们调用acct1中的withdraw方法,我们将得到这样的消息:

Sun Feb 06 17:45:45 ICT 2011 Insufficient ...

因为ShortLogger首先执行,然后他的super.log调用的是TimestampLogger。
但是,执行acct2的withdraw方法输出的是:

Sun Feb 06 1...

这里,TimestampLogger在特质列表中最后出现。器log方法首先被调用,之后调用ShortLogger被截断

5.在特质中重写抽象方法

//日志特质
trait Logger{
    def log(msg: String) //这是个抽象方法
}

现在,让我们用时间戳特质来扩展它,就像前一节中示例那样。很不幸,TimestampLogger 特质不能编译了。

//给日志添加时间戳
trait TimestampLogger extends Logged{
    override def log(msg: String){  //重写抽象方法
        super.log(new java.util.Date() + " " + msg) //super.log定义了吗?

    }
}

根据正常的继承规则,这个调用永远都是错的,Logger.log方法没有实现。但实际上,吉祥前一节看到的我们没法知道那个log方法最终被调用,这个取决于特质被混入的顺序。

Scala认为TimestampLogger 依旧是抽象的,因此必须给方法加上abstract关键字以及override关键字:

trait TimestampLogger extends Logged{
    abstract override def log(msg: String){  
        super.log(new java.util.Date() + " " + msg) 

    }
}

6.当做富借口是用的特质

特质包含了大量的工具方法,而这些工具方法可以依赖一些抽象方法来实现。

trait Logger{
    def log(msg: String) //这是个抽象方法
    def info(msg: String) {log("INFO: " + msg)}
    def warn(msg: String) {log("WARN: " + msg)}
    def servere(msg: String) {log("SEVERE: " + msg)}
}

注意我们是怎样把抽象方法和具体方法结合在一起的。

这样使用Logger特质的类就可以任意调用这些日志消息方法了。例如:

//存款类
class SavingsAccount extends Logged{
    def withdraw(amount: Double){
        if(amount: Double) servere("Insufficient funds")
        else 
        ...
    }

    ...

    override def log(msg: String){ println(msg); }

}

7.特质中的具体字段

特质中的字段可以试具体的可以试抽象额,如果给了初始值,name字段就是具体的。

通常,对于特质中每一个具体字段,使用该特制的类会获得一个字段预支对应。这些字段不是被继承的;他们只是简单的被加入到了子类当中。

在JVM中,一个类智能扩展一个超类,因此来自特质的字段不能以相同的方式继承。由于这个限制,特质中的字段被接入到了子类当中,并非父类字段的继承。

8.特质构造顺序

构造器以如下顺序执行:

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

例如:

class SavingAccount extends Account with FileLogger with ShortLogger

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

  1. Account(超类)
  2. Logger(第一个特质的父特质)
  3. FileLogger(第一个特质)
  4. ShortLogger(第二个特质)。注意他的父特质L噢GG而已被构造
  5. SavingsAccount(类)

9.初始化特质中的字段

特质不能有构造器参数。每个特质都有一个无参数的构造器。

说明:缺少构造器参数是特质与类之间唯一的技术差别

提前定义:

trait Logger {
    def log(msg: String)
}

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

class SavingsAccount extends Logged{
    def withdraw(amount: Double){
        if(amount: Double) servere("Insufficient funds")
        else 
        ...
    }

    ...

    override def log(msg: String){ println(msg); }

}

val acct = new SavingsAccount  with FileLogger {

val filename = "Hello" //将会报错,因为他的初始化顺序在FileLogger 之后,使得FileLogger初始化时filename 字段未完成初始化报错

}

解决办法:

val acct = new {
    val filename = "Hello"
} with SavingsAccount  with FileLogger 

如果你需要在类中做同样的事,可以这样

class SavingsAccount extends{
    val filename = "Hello"
}with FileLogger {

    ...

}

或者在FileLogger定义中使用懒值,但是并不高效

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

10.扩展类的特质

一种不常见的用法是,特质也可以扩展类。这个被特质扩展的类将会自动成为所有混入该特质的超类。

假定有如下代码:

trait LoggedException extends Exception with Logged{
    def log() { log(getMessage()) }
}

class UnhappyException extends LoggedException{

   ...

}

那么UnhappyException的超类自动变成LoggedException特质的扩展类Exception

如果我们的类已经扩展了另一个类怎么办?没关系,只要是特质的超类的一个子类即可。如果我们的类扩展了一个不相关的类,那么就不可能混入这个特质了。

11.自身类型

当特质扩展类时,编译器能够确保的一件事是所有混入该特质的类都认为这个类作超类。Scala还有另一套机制可以保证这一点:自身类型。

当特质以如下代码开始定义时:

this:类型 =>

它便只能被混入指定类型的子类

trait FileLogger extends Logger{
    this: Test => //Test必须是接口或者特质
    def log(msg: String){ println(msg); }
}

自身类型同样也可以处理结构类型,这种类型只给出类必须拥有的方法,而不是特质或接口名称:

trait LoggedException extends Logger{
    this: {def getMessage(): String} => 
    def log(msg: String){ println(msg); }
}

这个特质可以被混入任何拥有getMessage方法返回值类型为String的类

转载于:https://my.oschina.net/u/3687664/blog/2231554

相关文章:

  • linux安装LNMP环境之安装PHP
  • Graphviz的安装 - windows环境下
  • RSocket:一个面向反应式应用程序的新型应用网络协议
  • Spring Bean生命周期-registerBeanPostProcessors(七)
  • C#指定用户启动进程
  • ServletRequest和ServletResponse学习笔记
  • Ubuntu16.04 System program problem detected
  • MariaDB10.3 增补AliSQL补丁---安全执行Online DDL
  • shell脚本中打印所有匹配某些关键字符的行或前后各N行
  • 数组遍历的方法(loop)
  • 18-10-11
  • clipboard.js无法复制粘贴
  • android SAF存储访问框架
  • Oracle SQL执行计划基线总结(SQL Plan Baseline)
  • php相关笔记
  • Angularjs之国际化
  • JavaScript 基础知识 - 入门篇(一)
  • javascript从右向左截取指定位数字符的3种方法
  • JavaScript的使用你知道几种?(上)
  • js 实现textarea输入字数提示
  • ReactNative开发常用的三方模块
  • spring cloud gateway 源码解析(4)跨域问题处理
  • 对象引论
  • 浅谈Kotlin实战篇之自定义View图片圆角简单应用(一)
  • 深入浅出Node.js
  • 使用权重正则化较少模型过拟合
  • 问题之ssh中Host key verification failed的解决
  • 我感觉这是史上最牛的防sql注入方法类
  • 译自由幺半群
  • !!【OpenCV学习】计算两幅图像的重叠区域
  • #我与Java虚拟机的故事#连载03:面试过的百度,滴滴,快手都问了这些问题
  • #我与Java虚拟机的故事#连载11: JVM学习之路
  • (笔试题)合法字符串
  • (第9篇)大数据的的超级应用——数据挖掘-推荐系统
  • (二)Eureka服务搭建,服务注册,服务发现
  • (二十一)devops持续集成开发——使用jenkins的Docker Pipeline插件完成docker项目的pipeline流水线发布
  • (附源码)SSM环卫人员管理平台 计算机毕设36412
  • (力扣)1314.矩阵区域和
  • (一)VirtualBox安装增强功能
  • (一)插入排序
  • (转)JVM内存分配 -Xms128m -Xmx512m -XX:PermSize=128m -XX:MaxPermSize=512m
  • (总结)Linux下的暴力密码在线破解工具Hydra详解
  • ****** 二十三 ******、软设笔记【数据库】-数据操作-常用关系操作、关系运算
  • .apk文件,IIS不支持下载解决
  • .net6+aspose.words导出word并转pdf
  • ?php echo ?,?php echo Hello world!;?
  • @Autowired标签与 @Resource标签 的区别
  • @entity 不限字节长度的类型_一文读懂Redis常见对象类型的底层数据结构
  • @Transaction注解失效的几种场景(附有示例代码)
  • [] 与 [[]], -gt 与 > 的比较
  • []T 还是 []*T, 这是一个问题
  • [20160807][系统设计的三次迭代]
  • [3D基础]理解计算机3D图形学中的坐标系变换
  • [8-27]正则表达式、扩展表达式以及相关实战
  • [AIGC] 开源流程引擎哪个好,如何选型?