hangscer

Trait

2017/02/11

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字段不为空。

注意:对象混入特质、类中混入特质的区别和联系


拓展类的特质(extends SomeClass)

以下是一个示例。LoggedException特质拓展自Exception类。

这里的Exception是抽象类,那么类可以被特质拓展吗?
这也是可以的。
如下图,

将字节码反编译得到三个文件:Something.javaLogged.javaLoggedException,下面依次来解析:



我们可以看到,特质在扩展类时,用的是java8中的default方法。

现在创造一个混入该特质的类:

特质的超类也自动成为该类的超类:

问题:类已经拓展了另一个类怎么办
没关系,只要那是特质的超类的一个子类即可。

这里的UnhappyException扩展自NullPointerException,而NullPointerException已经扩展自Exception了。混入特质时,它的超类已经在那里了,不需要额外添加。


自身类型

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

当特质以如下this:类型=>开始定义时。它便只能被混入指定类型的子类。

这是不太正确的。注意如下:

注意 不需要扩展Exception类,而是有一个自身类型Exception 这意味着,它只能被被混入Exception的子类中。
在特质中,可以调用该自身类型的任何方法。比如getMessage,这个方法不是继承的,因为this一定是Exception的子类。不一定用this,任意名称就行,比如aa_this

自身类型比带有超类型的特质更灵活。

处理结构类型(structural type)

结构类型只要给出必须拥有的方法就行,而不是类的名称。如下:

或者

SomeClass{def getMsg():String}{def getMsg():String}的区别与联系???!!!