ScalaNote12-特质trait

网友投稿 844 2022-11-10

ScalaNote12-特质trait

ScalaNote12-特质trait

介绍特质相关内容,知识点多且杂,力有未逮~

Intro

特质是为了抽样出不同类共有的部分,方便在各个类中进行调用,这似乎有点像父类和子类继承的意思,有啥不同?Scala中类继承为单一继承,也就是说子类只能有一个父类,但是一个子类可以"继承"or调用多个Trait。当一个类可以和多个Trait混合,这些Trait定义的成员变量和方法也就变成了该类的成员变量和方法。

trait 特质名 { trait体}

trait 命名 一般首字母大写在scala中,java中的接口可以当做特质使用

trait的使用 一个类具有某种特质(特征),就意味着这个类满足了这个特质(特征)的所有要素,所以在使用时,也采用了extends关键字,如果有多个特质或存在父类,那么需要采用with关键字连接

没有父类:​​class 类名 extends 特质1 with 特质2 with 特质3 ..​​有父类:​​class 类名 extends 父类 with 特质1 with 特质2 with 特质3 ..​​

特质的注意点

Scala提供了特质(trait),特质可以同时拥有抽象方法和具体方法,一个类可以实现/继承多个特质特质中没有实现的方法就是抽象方法。类通过extends继承特质,通过with可以继承多个特质所有的java接口都可以当做Scala特质使用

看一个Demo,简单了解Trait的用法

//按照要求定义一个traittrait Trait01 { //定义一个规范 def getConnect()}//先将六个类的关系写出class A {}class B extends A {}// B、C都是A的子类,且C类引用了Trait01,并且重写了getConnect方法class C extends A with Trait01{ override def getConnect(): Unit = { println("Connect to MySQL...") }}// E、F继承父类D,同样在F类中重写getConnect方法class D {}class E extends D {}class F extends D with Trait01 { def getConnect(): Unit = { //抽象类override不是必写的? println("Connect to Oracle...") }}val c = new C()val f = new F()c.getConnect() // 连接mysql数据库...f.getConnect() // 连接oracle数据库..

Connect to MySQL...Connect to Oracle...defined trait Trait01defined class Adefined class Bdefined class Cdefined class Ddefined class Edefined class Fc: C = C@667f7558f: F = F@11c1d4c0

动态混入

前面我们在定义类时,引用了trait。如果后面想给该类的对象增加某些方法,可以在定义类处修改。而动态混入可以在新建对象时,加入特质,继承特质中方法。还是先看个demo:

trait UpdateData { def insert(id: Int): Unit = { println("Insert Records : " + id) }}class Mysql{ val user="root"}// 这里必须用withval mysql = new Mysql with UpdateDatamysql.insert(100)println("mysql.user = "+mysql.user)

Insert Records : 100mysql.user = rootdefined trait UpdateDatadefined class Mysqlmysql: Mysql with UpdateData = $anon$1@623ffd84

动态混入的特点

抽象类的动态混入有点不同,尤其是有抽象方法的抽象类,还是看个demo:

trait UpdateData { //特质 def insert(id: Int): Unit = { //方法(实现) println("Insert Records : " + id) }}abstract class MySQL1 { //空}// 带有抽象方法的抽象类abstract class MySQL2 { //空 def say()}//在不修改 类的定义基础,让他们可以使用trait方法// 这个和之前普通类的动态混入使用方法一样val mySQL1 = new MySQL1 with UpdateData//如果一个抽象类有抽象方法,如何动态混入特质// 抽象类不同实例化,这里相当匿名子类??有的内容又忘掉了val mySQL2 = new MySQL2 with UpdateData { override def say(): Unit = { println("say hello") }}mySQL1.insert(200)mySQL2.insert(999)mySQL2.say()

Insert Records : 200Insert Records : 999say hellodefined trait UpdateDatadefined class MySQL1defined class MySQL2mySQL1: MySQL1 with UpdateData = $anon$1@57d6510fmySQL2: MySQL2 with UpdateData = $anon$2@592924ff

可以看到动态混入可以新建对象,之前我们还学过其他方式:

new 对象//常规用法apply 创建//伴生对象和伴生类匿名子类方式//抽象类

叠加特质

trait UpdateData1 { //特质 println("UpdateData1...") def insert1(id: Int): Unit = { //方法(实现) println("UpdateData1-Insert Records : " + id) }}trait UpdateData2 { //特质 println("UpdateData2...") def insert2(id: Int): Unit = { //方法(实现) println("UpdateData2-Insert Records : " + id) }}class MySQL { //空 println("MySQL Is Here")}val mysql = new MySQL with UpdateData1 with UpdateData2mysql.insert1(100)

MySQL Is HereUpdateData1...UpdateData2...UpdateData1-Insert Records : 100defined trait UpdateData1defined trait UpdateData2defined class MySQLmysql: MySQL with UpdateData1 with UpdateData2 = $anon$1@a186ddf

重点关注执行顺序:

new MySQL时,先执行类里的代码//这个和类继承,先执行父类不同,见下面的案例混入多个特质时,从左至右,依次执行//这个时候又有点像类的继承,先执行父类里的代码。。。并且如果混入多个特质,特质之间不能方法名一致,否则报错

class MySQL { //空 println("MySQL Is Here")}class MySQL1 extends MySQL { //空 println("MySQL1 Is Here")}class MySQL2 extends MySQL1 { //空 println("MySQL2 Is Here")}new MySQL2

MySQL Is HereMySQL1 Is HereMySQL2 Is Heredefined class MySQLdefined class MySQL1defined class MySQL2res5: MySQL2 = MySQL2@73e3478a

trait Operate4 { //特质 println("Operate4...") def insert(id: Int) //抽象方法}trait Data4 extends Operate4 { //特质,继承Operate4 println("Data4") override def insert(id: Int): Unit = { //实现/重写 Operate4 的inser,override可以省略 println("Insert Records : " + id) }}trait DB4 extends Data4 { //特质,继承 Data4 println("DB4") override def insert(id: Int): Unit = { // 重写 Data4 的insert,override不可省略 println("To DB") super.insert(id) }}trait File4 extends Data4 { //特质,继承 Data4 println("File4") override def insert(id: Int): Unit = { // 重写 Data4 的insert println("To File") super.insert(id) //调用了insert方法(难点),这里super在动态混入时,不一定是父类 //如果我们希望直接调用Data4的insert方法,可以指定,如下 //说明:super[?] ?的类型,必须是当前的特质的直接父特质(超类)// super[Data4].insert(id) }}class MySQL4 { println("MySQL4...")} //普通类val mysql = new MySQL4 with DB4 with File4

MySQL4...Operate4...Data4DB4File4defined trait Operate4defined trait Data4defined trait DB4defined trait File4defined class MySQL4mysql: MySQL4 with DB4 with File4 = $anon$1@36c444df

traitd的继承关系

Operate4

Data4

DB4File4

创建 MySQL4实例时,动态的混入 DB4 和 File4,按照之前介绍的,构建顺序应该先构建类,在从左往右执行trait代码,但是trait本身也有继承关系,会先执行父trait代码。因此输出为:

先执行MySQL4中的 println(“MySQL4…”)trait DB4继承Data4,Data4继承Operate4,所以执行顺序先倒过来,Operate4->Data4->DB4File4继承逻辑和DB4一致,但是由于执行DB4时把父类的代码都跑一边了,因此只需要执行File4代码

Demo1里讲叠加特质时,特质中不能有重名方法。但是这个Demo2里又可以搞了??是因为他们都是Data4的子trait?不管咋样,代码执行时,都要指定导致是哪一个方法。老规矩,还是看个Demo3:

mysql.insert(999)

To FileTo DBInsert Records : 999

这个输出结果很奇葩,我试着复述下韩老师的解答:

调用方法时,从右往左执行因此先调用File4中的insert方法,执行println(“To File”)而super.insert时,并不是调用父特质(是这样叫?),而是调用左边DB4的insert方法所以转到DB4的insert,先println(“To DB”),完了接着super。此时左边没有trait了,直接调用父特质的方法最后执行Data4中的insert方法

针对上面的情况,也可以在File4中指定super的父特质,这样就不会调用DB4里的insert方法。

trait File4 extends Data4 { //特质,继承 Data4 println("File4") override def insert(id: Int): Unit = { // 重写 Data4 的insert println("To File") super[Data4].insert(id) }}class MySQL4 { println("MySQL4...")} //普通类val mysql = new MySQL4 with DB4 with File4println("===============")mysql.insert(99)

MySQL4...Operate4...Data4DB4File4===============To FileInsert Records : 99defined trait File4defined class MySQL4mysql: MySQL4 with DB4 with File4 = $anon$1@4d194ecb

总结下:

特质中的重写抽象方法

上面提了一嘴,重写抽象方法时,override可以不加,比如:

trait Operate4 { println("Operate4...") def insert(id: Int) }trait Data4 extends Operate4 { println("Data4") override def insert(id: Int): Unit = { println("Insert Records : " + id) }}

但是在子特质重写抽象方法时,必须具体实现,也就是不能​​super.insert(id)​​,即:

trait Operate4 { println("Operate4...") def insert(id: Int) }trait Data4 extends Operate4 { println("Data4") override def insert(id: Int): Unit = { super.insert(id) }}

那么,如果我非要在子类中super父类的抽象方法,咋整?

在override前增加abstract关键字或者直接去掉super行,也就是子类中重新写个抽象方法

这个操作很神奇,为啥这样做呢?之前我们说过构建对象混入多个对象时,方法的实现是从右往左执行,此时super父trait是继承左边的trait,韩老师说这样我们就可以不继承父类的方法而直接继承左边trait的方法,这也太绕了吧。。。 算了,还是看个demo:

trait Operate4 { println("Operate4...") def insert(id: Int) }trait Data4 extends Operate4 { println("Data4")// abstract override def insert(id: Int): Unit = { // super.insert(id)// }}trait DB4 extends Operate4 { println("DB4") def insert(id:Int):Unit={ println("DB4 Insert Records : " + id) }}class MySQL{println("MySQL")}// 构建顺序// MySQL->Operate4...->DB4->Data4val mysql = new MySQL() with DB4 with Data4println("=============")mysql.insert(666)

MySQLOperate4...DB4Data4=============DB4 Insert Records : 666defined trait Operate4defined trait Data4defined trait DB4defined class MySQLmysql: MySQL with DB4 with Data4 = $anon$1@655f55fe

这个用法很奇怪:

DB4和Data4必须是同继承一个父trait,不然他们有同名 insert方法会报错从代码逻辑上讲,如果DB4里有一个我们要用到的insert,干嘛还要再Data4里画蛇添足的重写个抽象方法?另外一个点,new MySQL() with DB4 这接执行这个代码还是报错,因为重写的仍然是抽象方法,动态混入时必须重写该方法?一头雾水啊,主要是不知道这种操作在代码里有啥实际作用

富接口 即该特质中既有抽象方法,又有非抽象方法。比如:

trait Operate { def insert( id : Int ) //抽象 def pageQuery(pageno:Int, pagesize:Int): Unit = { //实现 println("分页查询") }}

特质中的字段

特质中可以定义具体字段,如果初始化了就是具体字段,如果不初始化就是抽象字段。混入该特质的类就具有了该字段,字段不是继承,而是直接加入类,成为自己的字段。还是看个Demo:

trait DB6 { var sal:Int //抽象字段 var opertype : String = "insert" def insert(): Unit = { }}class MySQL6 {}val mysql = new MySQL6 with DB6{ override var sal = 666}println("mysql.opertype = "+mysql.opertype)println("mysql.sal = "+mysql.sal)

mysql.opertype = insertmysql.sal = 666defined trait DB6defined class MySQL6mysql: MySQL6 with DB6 = $anon$1@2f896ecb

抽象字段在混入时,必须覆盖重写具体字段直接加入类如果类中有同名字段,报错

trait AA {println("A...")}trait BB extends AA {println("B....")}trait CC extends BB {println("C....")}trait DD extends BB {println("D....")}class EE {println("E...")}class FF extends EE with CC with DD { //先继承了EE类,然后再继承CC 和DD println("F....")}class KK extends EE { //KK直接继承了普通类EE println("K....")}new FF()println("====================")new KK with CC with DD

E...A...B....C....D....F....====================E...K....A...B....C....D....defined trait AAdefined trait BBdefined trait CCdefined trait DDdefined class EEdefined class FFdefined class KKres26: KK with CC with DD = $anon$1@1e157558

执行的顺序为:

调用当前类的超类构造器第一个特质的父特质构造器第一个特质构造器第二个特质构造器的父特质构造器, 如果已经执行过,就不再执行第二个特质构造器…重复4,5的步骤(如果有第3个,第4个特质)当前类构造器因此:先执行FF超类EE,此时println(“E…”)再执行特质CC,而CC继承BB,BB继承AA,因此执行顺序AA->BB->CC接着执行DD,DD也继承BB,但是BB执行过了,直接执行DD最后执行本类FF,println(“F…”)

第1种方式实际是构建类对象, 在混入特质时,该对象还没有创建,因此超类和特质先执行第2种方式实际是构造匿名子类,可以理解成在混入特质时,对象已经创建了,因此超类和本类先执行

特质继承类

之前讲了类可以extends特质,其实特质也可以继承类,Scala学起来真是有点头大。

特质可以继承类,以用来拓展该类的一些功能所有混入该特质的类,会自动成为那个特质所继承的超类的子类如果混入该特质的类,已经继承了另一个类(A类),则要求A类是特质超类的子类,否则就会出现了多继承现象,发生错误

老规矩,看个Demo:

trait LoggedException extends Exception{ def log(): Unit ={ println(getMessage()) // 方法来自于Exception类 }}class AA extends LoggedException{}class BB extends AA with LoggedException{}

此时:

特质LoggedException拥有类Exception的getMessage方法类AA混入了特质LoggedException,所以类AA也成为了Exception的子类类BB继承类AA,同时混入LoggedException,此时要求AA和必须是Exception的子类,否则出现多继承

特质的自身类型

自身类型:主要是为了解决特质的循环依赖问题,同时可以确保特质在不扩展某个类的情况下,依然可以做到限制混入该特质的类的类型。 这个循环引用有点蒙蔽,之前说​​​class Console extends AA with CC​​​必须要求AA和CC有一个共同父类,不然报错。但是如果AA和CC之间互相引用,则会报错。(怎么互相引用?有点奇怪) 看个Demo,感受下:

//Logger就是自身类型特质trait Logger { // 明确告诉编译器,我就是Exception,如果没有这句话,下面的getMessage不能调用 this: Exception => def log(): Unit ={ // 既然我就是Exception, 那么就可以调用其中的方法 println(getMessage) }}class Console extends Logger {} //此时报错,Console不是Exception子类,不能混入特质Loggerclass Console extends Exception with Logger//可以

Ref

​​[1] 韩顺平老师的scala课程​​

2020-03-08 于南京市栖霞区

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:SpringBoot拦截器的使用介绍
下一篇:Scala108-Array常用方法总结
相关文章

 发表评论

暂时没有评论,来抢沙发吧~