Scala入门到精通——第十一节 Trait进阶
本节主要内容
- trait构造顺序
- trait与类的比较
- 提前定义与懒加载
- trait扩展类
- 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进阶相关推荐
- Scala入门到精通——第二十一节 类型参数(三)-协变与逆变
本节主要内容 协变 逆变 类型通匹符 1. 协变 协变定义形式如:trait List[+T] {} .当类型S是类型A的子类型时,则List[S]也可以认为是List[A}的子类型,即List[S] ...
- Scala入门到精通——第十七节 类型参数(一)
本节主要内容 类型变量界定(Type Variable Bound) 视图界定(View Bound) 上界(Upper Bound)与下界(Lower Bound) 1. 类型变量界定(Type V ...
- Scala入门到精通——第十节 Scala类层次结构、Traits初步
本节主要内容 Scala类层次结构总览 Scala中原生类型的实现方式解析 Nothing.Null类型解析 Traits简介 Traits几种不同使用方式 1 Scala类层次结构 Scala中的类 ...
- Scala入门到精通——第七节:类和对象(二)
本节主要内容 单例对象 伴生对象与伴生类 apply方法 应用程序对象 抽象类 单例对象 在某些应用场景下,我们可能不需要创建对象,而是想直接调用方法,但是Scala语言并不支持静态成员,scala通 ...
- Scala入门到精通——第二十七节 Scala操纵XML
本节主要内容 XML 字面量 XML内容提取 XML对象序列化及反序列化 XML文件读取与保存 XML模式匹配 1. XML 字面量 XML是一种非常重要的半结构化数据表示方式,目前大量的应用依赖于X ...
- Scala入门到精通——第二十节 类型参数(二)
本节主要内容 Ordering与Ordered特质 上下文界定(Context Bound) 多重界定 类型约束 1. Ordering与Ordered特质 在介绍上下文界定之前,我们对Scala中的 ...
- Scala入门到精通——第六节:类和对象(一)
本节主要内容 1 类定义.创建对象 2 主构造器 3 辅助构造器 类定义.创建对象 //采用关键字class定义 class Person {//类成员必须初始化,否则会报错//这里定义的是一个公有成 ...
- Scala入门到精通——第五节 函数、高阶函数与闭包
本节主要内容 (一)函数字面量(值函数) (二)匿名函数 (三)函数的简化 (四)函数参数 (四)闭包 函数字面量(值函数) 函数字面量(function literal),也称值函数(functio ...
- Scala入门到精通——第四节 Set、Map、Tuple、队列操作实战
本节主要内容 mutable.immutable集合 Set操作实战 Map操作实战 Tuple操作实战 队列操作实战 栈操作实战 mutable.immutable集合 以下内容来源于Scala官方 ...
最新文章
- Happy New Year
- 简述可编程控制器硬件组态及网络通信的核心思想_智能硬件设计报价诚信经营...
- 基于java的http服务器
- python 箱线图_python-matplotlib | 箱线图及解读
- 第九十九期:可以编写代码的代码:代码生成的利与弊
- codeforces 486A-C语言解题报告
- 【C语言】结构和指针
- 【牛客小白月赛12】华华教月月做数学(快速幂+快速乘------模版题)
- loss 加权_Multi-Similarity Loss使用通用对加权进行深度度量学习-CVPR2019
- matlab遗传工具箱ga,用遗传算法工具箱(GA)识别Bouc-Wen模型微分方程参数
- no interpreter
- Dynamic CRM 2016 IFD配置(1)证书颁发机构配置
- 2018 年全年详细工作日、周末、节假日数据json
- 玩转python——帮你解决乡愁
- Win11 封杀第三方浏览器工具,不用 Edge 就不行
- 【autojs】Auto.js Pro陌陌点赞全脚本源代码
- 光通量发光强度照度亮度关系_光通量、发光强度、照度单位的关系
- NFS unmatched host
- python输出乘法式子(HLOJ)(完整解析)
- 看不见的共享电单车战争
热门文章
- 有一批共n个集装箱要装上2艘载重量分别为C1和C2的轮船,其中集装箱i的重量为wi,且 装载问题要求确定,是否有一个合理的装载方案可将这n
- C语言:L1-037 A除以B (10分)(解题报告)
- 43行代码AC——HDU 1757 A Simple Math Problem(矩阵快速幂,附快速幂讲解)
- 详细图文演示——排除启动类故障以及Linux操作系统引导、运行级别和优化启动等相关知识
- scrapy框架_入门Scrapy框架看这一篇文章就够了
- STM32F1如何切换到不同的型号
- java与jquery的选择器区别_java day44【JQuery 基础:概念,快速入门,JQuery对象和JS对象区别与转换,选择器,DOM操作,案例】...
- 讯飞tts语音引擎9.0_使用科大讯飞语音转文字的服务进行电话录音分析
- java mysql乱码_41、java与mysql乱码的问题
- win10系统的定位服务器,Win10系统无法开启定位功能的原因及解决方法