前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Scala专题系列(六) : Scala特质

Scala专题系列(六) : Scala特质

作者头像
用户5252199
发布2022-04-18 13:58:52
6730
发布2022-04-18 13:58:52
举报
文章被收录于专栏:大数据技术博文
在 Java 中,类可以实现任意数量的接口。这种模型非常适用于声明实现了多个抽象的类。不过,这类模型也存在一个明显的缺点。对于一些接口而言,使用该接口的所有类使用了样板代码实现接口的大量功能。在 Java 8 诞生之前, Java 未提供用于定义和使用这类可重用代码的内置机制。为此, Java必须使用特定的方法进行复用某一接口的实现代码。

Java 8 做出了改变。现在我们可以在接口中定义方法,这些方法被称为 defender 方法或默认方法。实现类仍可以提供自己的实现。如果实现类未提供自己的实现的话, defender 方法会被调用。因此, Java 8 中的接口行为更接近于 Scala 中的 trait。但是, Java 8 中的接口与 Scala 中的 trait 仍有不同之处。 Java 8 中的接口只能定义静态字段,而 Scala 中的 trait 则可以定义实例级字段。这意味着 Java 8 中的接口无法管理实例状态。接口实现类必须提供字段以记录状态。这也意味着 defender 方法无法访问接口实现体的状态信息,从而限制了 defender 方法的用途。

Scala和Java一样不允许类从多个超类继承,在Java中类只能扩展 自一个超类,它可以实现多个接口,但接口中只能包含抽象方法,不能包含字段

Scala提供的"特质"类似于java中的接口, 但又有所不同,特质可以同时拥有抽象方法和具体方法,而类可以实现多个特质,同时这也很好的解决了java接口的问题

Scala特质可以如同java接口那样工作

举例:

代码语言:javascript
复制
trait Logger{
    def log(msg:String) // 这是一个抽象方法
}

在scala特质中,不需要将方法声明为abstract ,特质中未被实现的方法默认就是抽象的,同时子类的实现如下:

代码语言:javascript
复制
class consoleLogger extends Logger{  // 子类要实现某个特质,用extends而非implements
    def log(msg:String){ println(msg) }  // 重写的时候不需要用override关键字
}

如果我们需要实现多个特质的话, 用with关键字来添加其它的特质

class ConsoleLogger extends Logger with Cloneable with Serializable

Scala类只能有一个超类,但可以有任意数量的特质

特质里面也是可以有具体实现

在scala中,特质中的方法并不需要一定是抽象的,举例来说

代码语言:javascript
复制
trait consoleLogger{
    def log(msg:Stirng){ println(msg) }
}

在上面我们将consoleLogger改为了一个特质,并提供了一个带有实现的方法,该方法时将日志信息输出到控制台中.

具体的使用:

代码语言:javascript
复制
class SavingAcount extends Account with CosnoleLogger{
    def withdraw(amount:Double){
     if(amount > balance) log("insufficient funds")
     else balance -= amount
 }
}

SavingAccount 从ConsoleLogger特质得到了一个具体的log方法实现 (如果是java的话,这样是不行的)

在对象创建时加入特质

在构建单个对象时,也可以添加特质,比如:

代码语言:javascript
复制
trait Logger{
    def log(msg : String){}
}
class SavingAccount extends Account with Logger{
    def withdraw(amount:Double){
     if(amount > balance) log("balabala")
 }
}

在上述中,SavingAccount 中的log是不会输出到控制台的,因为在特质logger中,对于log方法没有任何的逻辑.

但是我们加入混入元素,

代码语言:javascript
复制
trait ConsoleLogger extends Logger{
    override def log(msg:String){ println(msg) }
}

然后可以在构造对象的时候,加入特质:

val acct = new SaveingAccount with ConsoleLogger

然后,当我们在acct对象上调用log方法时,consoleLogger特质的log方法就会被执行

特质的构造顺序

我们可以为类或对象添加多个互相调用的特质,从最后一个开始,这对于需要分阶段加工处理某个值得场景是很有用的

比如:

代码语言:javascript
复制
trait TimeStampLogger extends Logger {
    override def log(msg :String){
     super.log(new java.util.Date() + " " + msg)
 }
}

比如:

class SavingAccount extends Account with FileLogger with ShortLogger

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

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

如果上面代码的构造顺序如下:

1:Account(超类)

2:Logger(第一个特质的父特质)

3:FileLogger(第一个特质)

4:ShortLogger(第二个特质) // 此时父特质Logger已经被构造

5:SavingAccount(类)

特质中重写抽象方法

前面我们提到,在类中实现某个特质的方法时,是不需要加override的,但是在特质中,如果要重写父特质的方法时,需要加上override的

比如:

代码语言:javascript
复制
triait Logger{
    def log(msg:String) // 其实这个是一个抽象方法
}
然后另一个时间戳特质来扩展Logger特质
trait TimestampLogger extends Logger{
    override def log(msg:String){  // 重写抽象方法
     super.log(new Date() +  " " + msg ) 
 }
}

因为在Logger中,log方法时一个抽象方法,那么子特质在扩展父特质的时候,抽象方法要这样来修饰

代码语言:javascript
复制
abstract override def log(msg:String){
    super.log(new Date() + ""+msg)
}

上面这种做法看上去也是不是很好的,如果我们在TimestampLogger中 用abstract override 修饰了,那么在有类继承了TimestampLogger的时候,就会报出异常.

所以在修饰log的时候,

在特质中字段可以是具体,也可以是抽象的,如果给出了初始值,那么字段就是具体的

代码语言:javascript
复制
trait TimestampLogger extends Logger{
    val time = new Date()
}

混入该特质的类自动获得一个time字段,在特质中的每个具体字段,使用该特质得会获得一个字段与之对应,这些字段不能够被继承,它们只是简单的被加到了子类当中.

比如:

代码语言:javascript
复制
class Acount extends TimestampLogger{
    var balance = 1.1
}
class SavingAccount extends Account{
    var interset = 0.0
}

SavingAccount 类按正常的方式继承了这个字段,SavingAccount对象由所有超类的字段,以及任何子类中定义的字段构成.

另外特质中不能有构造器参数,每个特质都有一个无参数的构造器

特质没有构造器参数是特质与类之间的其中一个差别,其它的特质具备类的所有特性,比如具体的和抽象的字段,以及超类

特质扩展类

上面我们看到了特质可以扩展另一个特质,而同时,特质也可以扩展类,这个类将会自动成为所有混入该特质的超类.

比如:

代码语言:javascript
复制
trait LoggerException extends Exception with Logger{
    def log(){ log(getMessage()) }
}

LoggerException 有一个log方法用来记录异常的消息,同时,log方法调用了Exception超类继承下来的getMessage()方法

接下来,在来声明一个来混入该特质得类UnHappyException

代码语言:javascript
复制
class UnHappyException extends LoggerException{ // 该类扩展自一个特质
    override def getMessgae() = "error"
}

在上述代码中,特质的超类Exception自动的成为了类UnHappyException的超类

如果类已经扩展了另一个类,只要那是特质的超类的一个子类即可

比如:

代码语言:javascript
复制
class UnHappyException extends IOExceptin with LoggerException 

上面代码是可以的,因为UnHappyException继承了IOException,而IOException是扩展自Exception

自身类型:

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

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

代码语言:javascript
复制
this:类型 => 

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

比如:

代码语言:javascript
复制
trait LoggerException extends Logger{
    this : Exception => 
     def log(){ log(getMessage()) }
}

该特质并不扩展自Exception类,而是有一个自身类型Exception,这意味着,它只能被混入Exception的子类

在特质方法中,我们可以调用自身类型的任何方法,比如,log方法中的getMessage()调用时合法的.因为this就是一个exception类型,而下面代码

val h = new Home with LoggerException

上面代码是不合法的,因为Home不是Exception的子类型

同时,特质还可以处理结构类型,这种类型只给出类必须拥有的方法,而不是类的名称,比如:

代码语言:javascript
复制
trait LoggerException extends Logger{
    this : { def getMessage():String } => 
     def log() { log(getMessgae()) }
}

这个特质可以被混入任何拥有getMessage方法的类

特质内部的实现

Scala需要将特质翻译为JVM的类和接口, 只有抽象方法的特质被简单的编程了一个Java接口,比如:

代码语言:javascript
复制
trait Logger{
    def log(msg:String)
}

直接被翻译为:

代码语言:javascript
复制
public interface Logger{
    void log(String:msg)
}

如果特质由具体的方法,Scala会帮我们创建出一个伴生类,该伴生类用静态方法存放特质的方法.

比如:

代码语言:javascript
复制
trait ConsoleLogger extends Logger{
    def log(msg:String){ println(msg) }
}

被翻译为:

代码语言:javascript
复制
public interface ConsoleLogger extends Logger{
    void log(String msg)
}
public class ConsoleLogger$class{ // 伴生类
    public static void log(Console self,Stirng msg){
     println(msg)
 }
}

特质中的字段对应到接口中的抽象的getter和setter方法,当某个类实现该特质时,字段被自动加入

比如:

代码语言:javascript
复制
trait TimeLogger extends Logger{
    val time = 1
}

被译为:

代码语言:javascript
复制
public interface TimeLogger extends Logger{
    public abstract int time();
    public abstract void weird_profix$time_$sq(int);
}

weird开头的setter方式是需要的,用来初始化该字段,初始化发生在伴生类的一个初始化方法内

代码语言:javascript
复制
public class TimeLogger$class{
    public void $init$(TimeLogger self){
     self.weird_prefix$time_$eq(15)
 }
}

当特质被混入类的时候,类将会得到一个带有getter和setter的time字段,那个类的构造器会调用初始化方法

如果特质扩展自某个超类,则伴生类并不继承这个超类,该超类会被任何实现该特质的类继承

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2017-09-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 大数据技术博文 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
日志服务
日志服务(Cloud Log Service,CLS)是腾讯云提供的一站式日志服务平台,提供了从日志采集、日志存储到日志检索,图表分析、监控告警、日志投递等多项服务,协助用户通过日志来解决业务运维、服务监控、日志审计等场景问题。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档