hangscer

Trait

scala提供”特质”而非接口。特质类似接口,同时拥有抽象方法和具体方法,而类可以实现多个特质。

缺少构造器参数是特质与类(抽象类)之间的唯一技术差别(除了多继承问题)?!!

特质用法
当做接口使用的特质
带有具体实现的特质

当做接口使用的特质


def log(msg:String)不需要声明为abstract——特质中未被实现的方法默认就是抽象的。

scala并没有一个特殊的关键词用来标记对特质的实现。比起java接口,特质与类更像。

第一个特质前使用extends,但是在所有其他特质前使用with


带有具体实现的特质


在Scala中,特质中的方法并不需要一定是抽象的。
对于上图中的代码,我们说:ConsoleLogger的功能被混入了SavingsAccount类中。


当做富接口使用的特质

特质可以包含大量工具方法。如scala中的Iterator特质。

上图中,在特质中结合使用具体和抽象方法非常普遍。在java中需要声明一个接口和一个额外的拓展该接口的类(Collection/AbstractCollectionMouseListener/MouseAdapter)。


特质中的具体字段


这个特质得到的字节码反编译得到:

以上是对于var具体变量而言。
接下来查看val具体变量有何不同。


反编译得到:


通常,对于特质中的每一个具体字段,使用该特质的类都会获得一个字段与之对应。这些字段不是继承的,它们只是简单的添加进去而已。

可以看出,trait中具体方法的实现是用java8中的default方法实现的。


特质中的抽象字段

特质中未初始哈的字段在具体子类中必须被重写。

注意途中abstract override非常重要。

abstract override、abstract override

scala认为trait ShortLogger依然是抽象的——它必须混入一个具体的log方法,所以必须加上abstract override


特质构造顺序

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

SavingAccount.class反编译得到:

可以看到,SavingAccount构造函数如下执行顺序:

  1. Account(超类)
  2. FileLogger的超类Logger(第一个特质的父特质)
  3. FileLogger(第一个特质,init方法)
  4. ShortLogger(第二个特质),它的父特质已经被初始化了
  5. SavingsAccount(类,以及构造器里部分代码)

构造器的顺序是线性的。


初始化特质中的字段

特质不能有构造器参数。每个特质都有一个午参数的构造器。
不能写成val acct=new SavingsAccount with FileLogger("myapp.log"),这是错误的,特质不可以有构造器参数。
有一个可行方案,FileLogger可以有一个抽象字段来存放文件名。

使用该特质的类可以重写fileName字段。这里有一个陷阱

val acct=new SavingsAccount with FileLogger{val fileName="myapp.log"},不过这样是不对的。不对的。

那么为什么不对呢?
FileLogger构造器先于子类构造器执行。那么这里的子类SavingsAccount吗?不是!!
new语句构造的是拓展自SavingsAccount(超类)并混入FileLogger特质的匿名类的实例(联想结构类型)。fileName的初始化发生在这个匿名子类中————实际上这不会发生,因为在轮到子类之前,抛出了空指针异常。
解决办法有:

  • 提前定义
  • FileLogger构造器中使用懒值

new {...}语法


形如new {val fileName="myapp.log"},创造了一个匿名内部类。

提前定义


这段代码不美观,确实解决了问题。

如果需要在类中做同样的事情,语法如下:

使用懒值


如此一来,out字段将在初次使用时被初始化,而那时,fileName字段不为空。