本节主要内容

  1. trait构造顺序
  2. trait与类的比较
  3. 提前定义与懒加载
  4. trait扩展类
  5. self type

1 trait构造顺序

在前一讲当中我们提到,对于不存在具体实现及字段的trait,它最终生成的字节码文件反编译后是等同于Java中的接口,而对于存在具体实现及字段的trait,其字节码文件反编译后得到的java中的抽象类,它有着Scala语言自己的实现方式。因此,对于trait它也有自己的构造器,trait的构造器由字段的初始化和其它trait体中的语句构成,下面是其代码演示:

package cn.scala.xtwyimport java.io.PrintWritertrait Logger{println("Logger")def log(msg:String):Unit
}trait FileLogger extends Logger{println("FilgeLogger")val fileOutput=new PrintWriter("file.log")fileOutput.println("#")def log(msg:String):Unit={fileOutput.print(msg)fileOutput.flush()}
}
object TraitDemo{def main(args: Array[String]): Unit = {//匿名类new FileLogger{  }.log("trat demo")}
}
//打印输出内容为:
Logger
FilgeLogger
//创建文件file.log,内容为
#
trat demo

通过上述不难发现,在创建匿名类对象时,先调用的是Logger类的构造器,然后调用的是FileLogger的构造器。实际上构造器是按以下顺序执行的:
1. 如果有超类,则先调用超类的构造器
2. 如果有父trait,它会按照继承层次先调用父trait的构造器
2. 如果有多个父trait,则按顺序从左到右执行
3. 所有父类构造器和父trait被构造完之后,才会构造本类

class Person
class Student extends Person with FileLogger with Cloneable
上述构造器的执行顺序为:
1 首先调用父类Person的构造器
2 调用父trait Logger的构造器
3 再调用trait FileLogger构造器,再然后调用Cloneable的构造器
4 最后才调用Student的构造器
  • 1

2 trait与类的比较

通过前一小节,可以看到,trait有自己的构造器,它是无参构造器,不能定义trait带参数的构造器,即:

//不能定义trait带参数的构造器
trait FileLogger(msg:String) 
  • 1

除此之外 ,trait与普通的scala类并没有其它区别,在前一讲中我们提到,trait中可以有具体的、抽象的字段,也可以有具体的、抽象的方法,即使trait中没有抽象的方法也是合理的,如:

//FileLogger里面没有抽象的方法
trait FileLogger extends Logger{println("FilgeLogger")val fileOutput=new PrintWriter("file.log")fileOutput.println("#")def log(msg:String):Unit={fileOutput.print(msg)fileOutput.flush()}
}

3. 提前定义与懒加载

前面的FileLogger中的文件名被写死为”file.log”,程序不具有通用性,这边对前面的FileLogger进行改造,把文件名写成参数形式,代码如下:

import java.io.PrintWritertrait Logger{def log(msg:String):Unit
}trait FileLogger extends Logger{//增加了抽象成员变量val fileName:String//将抽象成员变量作为PrintWriter参数val fileOutput=new PrintWriter(fileName:String)fileOutput.println("#")def log(msg:String):Unit={fileOutput.print(msg)fileOutput.flush()}
}
  • 1

这样的设计会存在一个问题,虽然子类可以对fileName抽象成员变量进行重写,编译也能通过,但实际执行时会出空指针异常,完全代码如下:

package cn.scala.xtwyimport java.io.PrintWritertrait Logger{def log(msg:String):Unit
}trait FileLogger extends Logger{//增加了抽象成员变量val fileName:String//将抽象成员变量作为PrintWriter参数val fileOutput=new PrintWriter(fileName:String)fileOutput.println("#")def log(msg:String):Unit={fileOutput.print(msg)fileOutput.flush()}
}class Person
class Student extends Person with FileLogger{//Student类对FileLogger中的抽象字段进行重写val fileName="file.log"
}object TraitDemo{def main(args: Array[String]): Unit = {new Student().log("trait demo")}
}
  • 1

上述代码在编译时不会有问题,但实际执行时会抛异常,异常如下:

Exception in thread "main" java.lang.NullPointerExceptionat java.io.FileOutputStream.<init>(Unknown Source)at java.io.FileOutputStream.<init>(Unknown Source)at java.io.PrintWriter.<init>(Unknown Source)at cn.scala.xtwy.FileLogger$class.$init$(TraitDemo.scala:12)at cn.scala.xtwy.Student.<init>(TraitDemo.scala:22)at cn.scala.xtwy.TraitDemo$.main(TraitDemo.scala:28)at cn.scala.xtwy.TraitDemo.main(TraitDemo.scala)

具体原因就是构造器的执行顺序问题,

class Student extends Person with FileLogger{//Student类对FileLogger中的抽象字段进行重写val fileName="file.log"
}
//在对Student类进行new操作的时候,它首先会
//调用Person构造器,这没有问题,然后再调用
//Logger构造器,这也没问题,但它最后调用FileLogger
//构造器的时候,它会执行下面两条语句
//增加了抽象成员变量val fileName:String//将抽象成员变量作为PrintWriter参数val fileOutput=new PrintWriter(fileName:String)
此时fileName没有被赋值,被初始化为null,在执行new PrintWriter(fileName:String)操作的时候便抛出空指针异常

有几种办法可以解决前面的问题:
1 提前定义
提前定义是指在常规构造之前将变量初始化,完整代码如下:

package cn.scala.xtwyimport java.io.PrintWritertrait Logger{def log(msg:String):Unit
}trait FileLogger extends Logger{val fileName:Stringval fileOutput=new PrintWriter(fileName:String)fileOutput.println("#")def log(msg:String):Unit={fileOutput.print(msg)fileOutput.flush()}
}class Person
class Student extends Person with FileLogger{val fileName="file.log"
}object TraitDemo{def main(args: Array[String]): Unit = {val s=new {//提前定义override val fileName="file.log"} with Students.log("predifined variable ")}
}
  • 1

显然,这种方式编写的代码很不优雅,也比较难理解。此时可以通过在第一讲中提到的lazy即懒加载的方式

2 lazy懒加载的方式

package cn.scala.xtwyimport java.io.PrintWritertrait Logger{def log(msg:String):Unit
}trait FileLogger extends Logger{val fileName:String//将方法定义为lazy方式lazy val fileOutput=new PrintWriter(fileName:String)//下面这条语句不能出现,否则同样会报错//因此,它是FileLogger构造器里面的方法//在构造FileLogger的时候便会执行//fileOutput.println("#")def log(msg:String):Unit={fileOutput.print(msg)fileOutput.flush()}
}class Person
class Student extends Person with FileLogger{val fileName="file.log"
}object TraitDemo{def main(args: Array[String]): Unit = {val s=new Students.log("predifined variable ")}
}
  • 1

lazy方式定义fileOutput只有当真正被使用时才被初始化,例子中,当调用 s.log(“predifined variable “)时,fileOutput才被初始化,此时fileName已经被赋值了。

4 trait扩展类

在本节的第2小节部分,我们给出了trait与类之间的区别,我们现在明白,trait除了不具有带参数的构造函数之外,与普通类没有任何区别,这意味着trait也可以扩展其它类

trait Logger{def log(msg:String):Unit
}
//trait扩展类Exception
trait ExceptionLogger extends Exception with Logger{def log(msg:String):Unit={println(getMessage())}
}
  • 1

如果此时定义了一个类混入了ExceptionLogger ,则Exception自动地成为这个类的超类,代码如下:

trait Logger{def log(msg:String):Unit
}trait ExceptionLogger extends Exception with Logger{def log(msg:String):Unit={println(getMessage())}
}//类UnprintedException扩展自ExceptionLogger
//注意用的是extends
//此时ExceptionLogger父类Exception自动成为
//UnprintedException的父类
class UnprintedException extends ExceptionLogger{override  def log(msg:String):Unit={println("")}
} 

当UnprintedException扩展的类或混入的特质具有相同的父类时,scala会自动地消除冲突,例如:

//IOException具有父类Exception
//ExceptionLogger也具有父类Exception
//scala会使UnprintedException只有一个父类Exception
class UnprintedException extends IOException with ExceptionLogger{override  def log(msg:String):Unit={println("")}
} 

5 self type

下面的代码演示了什么是self type即自身类型

class A{//下面 self =>  定义了this的别名,它是self type的一种特殊形式//这里的self并不是关键字,可以是任何名称self =>  val x=2 //可以用self.x作为this.x使用def foo = self.x + this.x
}

下面给出了内部类中使用场景

class OuterClass { outer => //定义了一个外部类别名val v1 = "here"class InnerClass {// 用outer表示外部类,相当于OuterClass.thisprintln(outer.v1) }
}

而下面的代码则定义了自身类型self type,它不是前面别名的用途,

trait X{}
class B{//self:X => 要求B在实例化时或定义B的子类时//必须混入指定的X类型,这个X类型也可以指定为当前类型self:X=>
}

自身类型的存在相当于让当前类变得“抽象”了,它假设当前对象(this)也符合指定的类型,因为自身类型 this:X =>的存在,当前类构造实例时需要同时满足X类型,下面给出自身类型的使用代码:

trait X{def foo()
}
class B{self:X=>
}
//类C扩展B的时候必须混入trait X
//否则的话会报错
class C extends B with X{def foo()=println("self type demo")
}object SelfTypeDemo extends App{println(new C().foo)
}

Scala入门到精通——第十一节 Trait进阶相关推荐

  1. Scala入门到精通——第二十一节 类型参数(三)-协变与逆变

    本节主要内容 协变 逆变 类型通匹符 1. 协变 协变定义形式如:trait List[+T] {} .当类型S是类型A的子类型时,则List[S]也可以认为是List[A}的子类型,即List[S] ...

  2. Scala入门到精通——第十七节 类型参数(一)

    本节主要内容 类型变量界定(Type Variable Bound) 视图界定(View Bound) 上界(Upper Bound)与下界(Lower Bound) 1. 类型变量界定(Type V ...

  3. Scala入门到精通——第十节 Scala类层次结构、Traits初步

    本节主要内容 Scala类层次结构总览 Scala中原生类型的实现方式解析 Nothing.Null类型解析 Traits简介 Traits几种不同使用方式 1 Scala类层次结构 Scala中的类 ...

  4. Scala入门到精通——第七节:类和对象(二)

    本节主要内容 单例对象 伴生对象与伴生类 apply方法 应用程序对象 抽象类 单例对象 在某些应用场景下,我们可能不需要创建对象,而是想直接调用方法,但是Scala语言并不支持静态成员,scala通 ...

  5. Scala入门到精通——第二十七节 Scala操纵XML

    本节主要内容 XML 字面量 XML内容提取 XML对象序列化及反序列化 XML文件读取与保存 XML模式匹配 1. XML 字面量 XML是一种非常重要的半结构化数据表示方式,目前大量的应用依赖于X ...

  6. Scala入门到精通——第二十节 类型参数(二)

    本节主要内容 Ordering与Ordered特质 上下文界定(Context Bound) 多重界定 类型约束 1. Ordering与Ordered特质 在介绍上下文界定之前,我们对Scala中的 ...

  7. Scala入门到精通——第六节:类和对象(一)

    本节主要内容 1 类定义.创建对象 2 主构造器 3 辅助构造器 类定义.创建对象 //采用关键字class定义 class Person {//类成员必须初始化,否则会报错//这里定义的是一个公有成 ...

  8. Scala入门到精通——第五节 函数、高阶函数与闭包

    本节主要内容 (一)函数字面量(值函数) (二)匿名函数 (三)函数的简化 (四)函数参数 (四)闭包 函数字面量(值函数) 函数字面量(function literal),也称值函数(functio ...

  9. Scala入门到精通——第四节 Set、Map、Tuple、队列操作实战

    本节主要内容 mutable.immutable集合 Set操作实战 Map操作实战 Tuple操作实战 队列操作实战 栈操作实战 mutable.immutable集合 以下内容来源于Scala官方 ...

最新文章

  1. Happy New Year
  2. 简述可编程控制器硬件组态及网络通信的核心思想_智能硬件设计报价诚信经营...
  3. 基于java的http服务器
  4. python 箱线图_python-matplotlib | 箱线图及解读
  5. 第九十九期:可以编写代码的代码:代码生成的利与弊
  6. codeforces 486A-C语言解题报告
  7. 【C语言】结构和指针
  8. 【牛客小白月赛12】华华教月月做数学(快速幂+快速乘------模版题)
  9. loss 加权_Multi-Similarity Loss使用通用对加权进行深度度量学习-CVPR2019
  10. matlab遗传工具箱ga,用遗传算法工具箱(GA)识别Bouc-Wen模型微分方程参数
  11. no interpreter
  12. Dynamic CRM 2016 IFD配置(1)证书颁发机构配置
  13. 2018 年全年详细工作日、周末、节假日数据json
  14. 玩转python——帮你解决乡愁
  15. Win11 封杀第三方浏览器工具,不用 Edge 就不行
  16. 【autojs】Auto.js Pro陌陌点赞全脚本源代码
  17. 光通量发光强度照度亮度关系_光通量、发光强度、照度单位的关系
  18. NFS unmatched host
  19. python输出乘法式子(HLOJ)(完整解析)
  20. 看不见的共享电单车战争

热门文章

  1. 有一批共n个集装箱要装上2艘载重量分别为C1和C2的轮船,其中集装箱i的重量为wi,且 装载问题要求确定,是否有一个合理的装载方案可将这n
  2. C语言:L1-037 A除以B (10分)(解题报告)
  3. 43行代码AC——HDU 1757 A Simple Math Problem(矩阵快速幂,附快速幂讲解)
  4. 详细图文演示——排除启动类故障以及Linux操作系统引导、运行级别和优化启动等相关知识
  5. scrapy框架_入门Scrapy框架看这一篇文章就够了
  6. STM32F1如何切换到不同的型号
  7. java与jquery的选择器区别_java day44【JQuery 基础:概念,快速入门,JQuery对象和JS对象区别与转换,选择器,DOM操作,案例】...
  8. 讯飞tts语音引擎9.0_使用科大讯飞语音转文字的服务进行电话录音分析
  9. java mysql乱码_41、java与mysql乱码的问题
  10. win10系统的定位服务器,Win10系统无法开启定位功能的原因及解决方法