一.本章要点

  • 类可以实现任意数量的特质
  • 特质可以要求实现它们的类具备特定的字段,方法或超类
  • 和Java接口不同,Scala特质可以提供方法和字段实现
  • 当你将多个特质叠加在一起时,顺序很重要——其方法先被执行的特质排在更后面

二.为什么没有多重继承

  Scala和Java一样,不允许使用多重继承(如果继承的多个超类具备某些共通的方法或字段,会引起混乱,还有可能引起菱形继承问题);

  Java中可以实现任意多个接口(接口中只能有抽象方法,且不能有字段,Java中使用抽象基类和接口的做法让可以实现一些方法[治标不治本,同时扩展两个基类会出问题]),Scala提供“特质”而不是接口,特质可以同时拥有抽象方法和具体方法,类可以实现多个特质。

三.当作接口使用的特质

  

trait Logger{
def log(msg:String) //抽象方法
} //子类实现(使用extends)
class ConsoleLogger extends Logger{
def log(msgLString){ //不需要写override
println(msg)
} }

  注:Scala并没有一个特殊的关键字标记特质的实现;

    使用with关键字添加多个特质(extends只需要一个,后面是一个整体,如class ConsoleLogger extends Logger with Cloneable with Serializable)

四.带有具体实现的特质

  

trait Logger{
def log(msg:String) {
println(msg)}
} //子类实现
class SavingAccount extends Account wirh Logger{
def withdraw(amount:Double){
if(amount>balance) log("Not enugth")
}
else ....
}

  注:

    这就是具有具体实现的特质,但是让特质具有具体行为有一个弊端:当特质改变时,所有混入该特质的类都必须重新编译。

五.带有特质的对象

  可以在构造对象时混入特质(可根据不同对象需要加入不同特质),例:

trait Logger{
def log(msg:String) {
}
} //子类实现
class SavingAccount extends Account wirh Logger{
def withdraw(amount:Double){
if(amount>balance) log("Not enugth")
}
else ....
} trait MyLogger extends Logger{
def log(msg:String) {println(msg)
}
} //实例化加特质
val acct=new SavingAccount with MyLogger

  

六.叠加在一起的特质

  可以为对象或类添加多个互相调用的特质,对于分阶段加工处理某个值的场景非常有用。

    

  super.log调用的是特质等级中的下一个特质(根据特质的添加顺序决定):

  特质super.xxx执行的方法只依赖于这些特质的对象或类给出的顺序(更加灵活);

  可以控制具体是哪一个特质的方法被调用:super[ConsoleLogger].log(...),这里的类型必须是直接超类型,不能使用继承等级更远的特质或类。

七.在特质中重写抽象的方法

  

traint Logger{
def log(msg:String) //抽象方法
}
//错误的
trait TimestampLogger extends Logger{
override def log(msg:String){//重写抽象方法
super.log(new java.util.Date()+" "+msg) }
} //正确使用
trait TimestampLogger extends Logger{
abstract override def log(msg:String){//重写抽象方法
super.log(new java.util.Date()+" "+msg) }
}

  Logger.log没有实现,Scala认为TimestampLogger依旧是抽象的,它需要混入一个具体的log方法,因此必须使用abstract和override关键字。

八.当做富接口使用的特质

  特质中可以使用抽象方法和具体方法(相当于Java中接口和抽象类一起实现),例:

trait Logger{
def log(msg:String)
def info(msg:String){log("Info:"+msg)}
def warn(msg:String){log("warn:"+msg)}
def service(msg:String){log("service:"+msg)} }

九.特质中的具体字段

  特质中的字段可以是具体的,也可以是抽象的。

  对于特质中的每一个字段,使用该特质的类都会获得特质中的字段(不是继承,只是简单的加入)。

十.特质中的抽象字段

  特质中未被初始化的字段在具体的子类中必须重写(不需要写override)。

十一.特质构造顺序

  和类一样,特质也有构造器,由字段的初始化和其他特质体中的语句构成。

  特质中的构造器语句在任何混入该特质的对象在构造时执行。

  构造器执行顺序:

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

  注:构造器的顺序是类的线性化的反向

十二.初始化特质中的字段

  特质不能有够构造器参数(类和特质的唯一区别),每个特质都有一个无参书的构造器。

  如果需要定制(如日志文件名),可以使用一个抽象字段来存文件名,注意这样的陷阱:

traint FileLogger extends Logger{
val filename:String
val out=new PrintStream(filename)
def log(msg:String){
out.println(msg);out.rlush()
}
} //错误的(FileLogger的构造器先于子类构造器执行,所以FileLogger的构造器会抛出一个空指针异常)
acct =new SavingsAccount with FileLogger(){
val filename="myapp.log"
}
//解决方法一:可以使用“提前定义“解决
val acct=new {//new之后的提前定义块
val filename="myapp.log"
}with SavingsAccount with FileLoggere
//类中使用提前定义
class SavingAccount extends {//extends后的提前定义块
val filename="myapp.log"
}with Account with FileLogger{
...//SavingsAccount的实现
}
//解决方法二:使用懒值(并不是那么高效)
trait FileLogger extends Logger{
val filename:String
lazy val out=new PrintStream(filename)
def log(msg:String){out.println(msg)//不需要重写override}}

  

十三.扩展类的特质

  特质可以扩展另一个特质,同时特质也可以扩展类(这个类将会自动成为所有混入该特质的超类),例:

 

//LoggedException 扩展自Exception类

trait LoggedException extends Exception with Logged{
def log(){
log(getMessage())
}
} //物质的超类也称为这个类的超类
class UnhappyException extends LogedException{ //该类扩展自一个特质
override def getMessage()="test"
}
//IOException是Exception的子类
class UnhappyException extends IOException with LoggedException{
}

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

十四.自身类型

  当特质扩展类时,编译器能确保所有混入这一特质的类都认这个类为超类,自身类型也能保证。

  自身类型定义如下:this:类型=>,例:

//该特质并不扩展Exception类,而是有一个自身类型Exception,因此只能混入Exception的子类,this一定是一个Excption
trait LoggedException extends Logged{
this :Exception=>{
def log(){
log(getMessage())}}}

  注:自身类型和带有超类型的特质很像,都能确保混入该特质的类都能使用某个特定类型的特性,在某些情况下,自身类型比超类版更灵活,而且自身类型可以解决循环依赖(有两个彼此需要的特质时循环依赖就产生了);

    自身类型也可以处理结构类型:只给出类必须拥有的方法,而不是类名称,例:

trait LoggedException extends Logged{
this :def getMessage():String=>{
def log(){
log(getMessage())}}}

十五.背后发生了什么

  Scala需要将特质翻译成JVM的类和接口。

  只有抽象方法的特质,例:

//Scala
trait Logger{
def log(msg:String)
}
//JVM对应生成的Java接口
public interface Logger{
void log(String msg);
}

  具有具体方法的特质,Scala会为我们创建伴生类,该伴生类用静态方法存放特质的方法,例:

//Scala代码
trait ConsoleLoggger extends Logger{
def log(msg:String){println(msg)}
}
//JVM对应Java代码
public interface ConsoleLogger extends Logger{
//生成的Java接口
void log(String msg);
} public class ConsoleLogger$class{
//生成的Java伴生类,注意伴生类中不会有任何字段
public static void log(ConsoleLogger self,String msg){
System.out.println(msg);
}
}

  伴生类中不会有任何字段,特质中的字段对应接口中的抽象的getter和setter方法,例:

//Scala代码
trait ShortLogger extends Logger{
val maxLength=15//具体字段
...}
//Java对应
public interface ShortLogger extends Logger{
public abstract int maxLength();
public abstract void weired_prefix$maxLength_$eq(int);
...
} public class ShortLogger$class{
//以weird开头的setter方法是需要的,用来初始化该字段
//当特质混入类时,类会有一个setter和getter方法和maxLength字段,类的构造器会调用初始化方法
public void $init$(ShortLogger self){
self.weird_prefix$maxLength_$eq(15)}

十六.练习

Scala学习十——特质的更多相关文章

  1. Scala学习十九——解析

    一.本章要点 文法定义中的二选一.拼接.选项和重复在Scala组合子解析器中对应|.~.opt和rep 对于RegexParsers而言,字符串字面量和正则表达式匹配的是词法单元 用^^来处理解析结果 ...

  2. Scala学习十八——高级类型

    一.本章要点 单例类型可用于方法串接和带对象参数的方法 类型投影对所有外部类的对象都包含了其他内部类的实例 类型别名给类型指定一个短小的名称 结构类型等效于”鸭子类型“ 存在类型为泛型的通配参数提供了 ...

  3. scala学习笔记——特质

    一个类扩展自一个或多个特质,以便使用这些特质提供的服务.特质可能会要求使用它的类支持某个特定的特性.不过和java不同,Scala特质可以给出这些特性的缺省实现. 特质的特性: 类可以实现任意数量的特 ...

  4. Scala学习笔记--特质trait

    http://outofmemory.cn/scala/scala-trait-introduce-and-example 与Java相似之处 Scala类型系统的基础部分是与Java非常相像的.Sc ...

  5. Scala学习十六——XML处理

    一.本章要点 XML字面量<like>this</like>的类型为NodeSeq 可以在XML字面量中内嵌Scala代码 Node的child属性产出后代节点 Node的at ...

  6. Scala学习十五——注解

    一.本章要点 可以为类.方法.字段.局部变量.参数.表达式.类型参数以及各种类型定义添加注解 对于表达式和类型,注解跟在被注解的条目之后 注解的形式有@Annotation.@Annotation(v ...

  7. Scala学习十四——模式匹配和样例类

    一.本章要点 match表达式是更好的switch,不会有意外调入下一个分支 如果没有模式能够匹配,会抛出MatchError,可以用case _模式避免 模式可以包含一个随意定义的条件,称做守卫 你 ...

  8. Scala学习十二——高阶函数

    一.本章要点 在Scala中函数是”头等公民“(可以作为参数,返回值,赋值给其他); 可以创建匿名函数,通常还会交给其他函数; 函数参数可以给出需要稍后执行的行为; 许多集合方法都接受函数参数,将函数 ...

  9. Scala学习笔记(七):Application特质

    Scala提供了特质scala.Application 在单例对象名后面写上“extends Application”,把想要执行的代码直接放在单例对象的花括号之间 import ChecksumAc ...

随机推荐

  1. C/C++程序基础-C++与C有什么不同

    1:C和C++的联系和区别? 答:C是一个结构化语言,它的重点在于算法和数据结构.对于语言本身而言,C是C++的子集.C程序的设计首先要考虑的是如何通过一个过程,对输入进行运算处理,得到输出.对于C+ ...

  2. dubbo学习笔记(一)超时与重试

    dubbo提供在provider和consumer端,都提供了超时(timeout)和重试(retries)的参数配置. 配置方式 provider端在<dubbo:service>中配置 ...

  3. [MyBatis]最简MyBatis工程

    下载地址:https://files.cnblogs.com/files/xiandedanteng/fillMillionDatum01_191005.rar --END-- 2019年10月5日1 ...

  4. RoP

    RoPS特征提取 RoPS为Rotational Projection Statistics的简写,即旋转投影统计特征.RoPS特征具有对点云旋转和平移(即姿态变化)的不变性,具备很强的鉴别力以及对噪 ...

  5. VBA ListView控件使用实例

    功能:ListView控件实现连接数据库实现显示查询数据空能. 图片: 代码: Private Sub CommandButton1_Click() Dim res As String res = T ...

  6. 执行scripts/mysql_install_db --user=mysql --basedir=/usr/local/mysql --datadir=/data/mysqldb命令时一直报错:

    Can't locate Data/Dumper.pm in @INC (@INC contains: /usr/local/lib64/perl5 /usr/local/share/perl5 /u ...

  7. linux 自定义函数

    用别人的车子出行,总感觉别扭,那怎么自定义自己的车轮子呢? 通过上面的求两个参数的和例子,我们可以学到定义一个函数基本的步骤, function getSum(){  SUM=$[$n1+$n2]  ...

  8. UUID相同导致的网络连接问题

    目录 场景 思路 解决过程 提升虚拟机配置 直连交换机 最终解决方案 总结 场景 有同事从公司寄了一台服务器到现场,用来安装数据库.缓存等组件供开发使用.到了之后,连接电源.网线,设置IP,用vSph ...

  9. tf.metrics.sparse_average_precision_at_k 和 tf.metrics.precision_at_k的自己理解

    tensorflow最大的问题就是大家都讲算法,不讲解用法,API文档又全是英文的,看起来好吃力,理解又不到位.当然给数学博士看的话,就没问题的. 最近看了一系列非常不错的文章,做一下记录: http ...

  10. APP安全测试要点

    APP面临的威胁 APP评估思路 APP自动化检测思路 安全测试要点 证书和签名 将apk文件更名为zip 使用unzip解压 META-INF中包含签名文件和真正的CERT.RSA文件(公钥证书自签 ...