scala特质

要点如下:

1.   Scala中类只能继承一个超类,可以扩展任意数量的特质

2.   Java接口不同, Scala特质可以提供方法和字段的实现

3.   当将多个特质叠加使用的时候,顺序很重要——其方法先被执行的特质    排在更后面。

 

1. Java 接口和 Scala 特质

1.1 Java 接口

在学习Scala特质之前,我们先来复习一下Java接口。

Java接口是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能)。

Java接口本身没有任何实现,因为Java接口不涉及表象,而只描述public行为,所以Java接口比Java抽象类更抽象化。

Java接口的方法只能是抽象的和公开的,Java接口不能有构造器,Java接口可以有public、静态的和final属性。

java8开始接口里可以有静态方式,用static修饰,但是接口里的静态方法的修饰符只能是public,且默认是publicjava8里,除了可以在接口里写静态方法,还可以写非静态方法,但是必须用default修饰,且只能是public,默认也是public。默认方法可以被继承。但是要注意,如果继承了两个接口里面的默认方法一样的话,那么必须重写。接口可以被实现,但是无法被实例化。

1.2 Scala特质

Scala Trait(特质) 相当于 Java 的接口,实际上它比接口还功能强大。与接口不同的是,它还可以定义属性和方法的实现。

Trait定义的方式与类类似,但它使用的关键字是 trait

例:

trait A {

val num: Int = 1

}

2. Scala类没有多继承

问题:为什么Scala不支持多重继承呢?

接下来我们看一个例题:

class A{

val id: Int = 01

}

class B {

val id: Int = 02

}

假设可以有:

class C extends A B {

 ...

}

问题:要求返回id时,该返回哪一个呢?

这就引出了菱形继承问题:

 

对于 class A 中的字段, class D B C 都得到了一份,这两个字段怎么得到和被构造呢?这样的情况显然是不合理的。

如果只是把毫不相关的类组装在一起,多继承不会出现问题,但如果这些类具备某些共同的字段或方法,则多继承就会出现问题,即多重继承会产生菱形继承问题。

那么,如何解决这种问题呢?

Java中取而代之的是接口,Scala中则是特质。

一般情况下Scala的类只能够继承一个超类,但是如果是Trait的话就可以继承多个,从结果来看就是实现了多重继承。

 

Scala 中类只能继承一个超类(Java中称为父类),可以扩展任意数量的特质,与Java接口相比,Scala 的特质可以有具体方法和抽象方法; Java 的抽象基类中也有具体方法和抽象方法,但Java的接口不能有具体方法。

扩展:什么时候应该使用特质而不是抽象类? 

如果你想定义一个类似接口的类型,你可能会在特质和抽象类之间难以取舍。这两种形式都可以让你定义一个类型的一些行为,并要求继承者定义一些其他行为。

一些经验法则:

Ø  优先使用特质。一个类扩展多个特质是很方便的,但却只能扩展一个抽象类。

Ø  如果你需要构造函数参数,使用抽象类。因为抽象类可以定义带参数的构造函数,而特质不行。

        例如,你不能说trait t(i:Int) {},参数i是非法的。


3. 当做接口使用的特质

 首先,让我们从熟悉的内容开始。Scala特质可以完全像Java接口一样工作。

例子:

trait Logger {

    //abstractmethod, but no abstract declare required

    def log(msg: String)       //没有实现,这是个抽象方法

}

注意:你不需要将抽象方法声明为 abstract特质中未被实现的方法默认就是抽象方法。

子类可以给出实现:

class ConsoleLogger extends Logger {

//在重写特质的抽象方法时不需要给出override关键字

    def log(msg: String){

        println(msg)

    }

}

4. 带有具体实现的特质

trait Logger {

def log(msg:String)  // 抽象方法

def printAny(k: Any){ // 具体方法

println("具体实现")

}

让特质混有具体行为有一个弊端. 当特质改变时,所有混入该特质的类都必须重新编译。

 

5. 带有特质的对象

Scala可以在创建对象时添加特质,这是Java接口所不具备的特性。

特质可以将对象原本没有的方法与字段加入对象中,如果特质和对象改写了同一超类的方法,则排在右边的先被执行。

例:

// Feline 猫科动物

abstract class Feline {

def say()

}

trait Tiger extends Feline {

// 在特质中重写抽象方法,需要在方法前添加 abstract override 2个关键字

abstract overridedef say() = println("")

def king() =println("I'm king of here")

}

class Cat extends Feline {

override def say() =println("")

}

object Test extends App {

val feline = new Catwith Tiger         //在对象构造时混入特质

feline.say  // Cat Tiger 都与 say 方法,调用时从右往左调用, Tiger 在叫

feline.king// 可以看到即使没有 cat 中没有 king 方法, Tiger 特质也能将自己的方法混入 Cat 中

}

输出结果:

 

6. 特质的叠加

就像Java Class可以实现多个接口一样,Scala Class也可以叠加多个特质。

6.1 with 关键字添加额外特质

类可以通过 extends 关键字继承特质,如果需要的特质不止一个,通过 with 关键字添加额外特质。

例: Class A extends Bwith C with D {…}

6.2特质的处理顺序

一般来说,特质从最后一个开始被处理。这对于需要分阶段加工处理某个值的场景很有用.

例:

class A {…}

trait B {…}

trait C {…}

object test extends App{

val a = new A with Bwith C

}

实际上,一个方法调用的是特质层级中的那一个特质,具体是哪一个,取决于特质添加的顺序。一般来说,特质从最后一个开始被处理。

在上面这个例子中,特质C中的方法首先会先被执行。

 

7. 特质中的字段

特质中的字段可以是具体的也可以是抽象的.

7.1 具体字段

如果给出了初始值那么字段就是具体的.

trait  Ability {

val run ="running" // 具体字段

}

7.2抽象字段

如果未出了初始值那么字段就是抽象的。特质中未被初始化的字段在具体的子类中必须被重写。

 

trait Ability {

val swim: String    //抽象字段

def ability(msg: String)= println(msg + swim)  //方法用了swim字段

}

class Cat extends Ability {

val swim ="swimming"

 }

object Test extends App{

val f = new Cat withAbility

val fish:String ="fish"

f.ability(fish)

}

运行结果:

 

这种提供特质参数的方式在临时构造某种对象很有利,很灵活,按需定制.

 

8. 特质的构造顺序

scala中除了对象(object)以外,其他的单位,例如类,特质等都有构造器。特质的构造器,由字段的初始化和其他特质体中的语句构成。

例:

trait FileLogger extends Logger

      val out =new PrintWriter(“app.log”)//这是特质构造器的一部分

      out.println(“#”+newDate().Tostring) //这也是特质构造器的一部分

val filename: String// 构造器一部分

    valout = new PrintWriter(filename) // 构造器的一部分

deflog(msg: String){ out.println(msg);out.flush() }

这些语句在任何混入该特质的对象在构造时都会被执行。

 

在这么多种的构造器中,在程序执行的过程中,构造器也有其特定的执行过程,具体的执行顺序如下:

1.   调用超类的构造器;

2.   特质构造器在超类构造器之后、类构造器之前执行;

3.   特质由左到右被构造;

4.   每个特质当中,父特质先被构造;

5.   如果多个特质共有一个父特质,父特质不会被重复构造

6.   所有特质被构造完毕,子类被构造。

 

举例考虑一下下面这个类构造器将按什么顺序执行:

trait Logger{…}  

trait ShortLogger extends Logger{…}  

trait TimestampLogger extends Logger{…}  

class Account{…}  

class SavingsAccount extends Account with FileLogger with ShortLogger

 

构造器将按如下顺序执行:

1.   Account(超类)

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

3.   FileLogger(第一个特质)

4.   ShortLogger(第二个特质)

5.   SavingsAccount(类)

 

注意与叠加特质时,特质被处理的顺序区分,一般来说,特质从右向左执行,即从最后一个开始被处理。而特质的构造是从左到右被构造

特质不能有构造器参数. 每个特质都有一个无参构造器. 值得一提的是,缺少构造器参数是特质与类唯一不相同的技术差别. 除此之外,特质可以具有类的所有特性,比如具体的和抽象的字段,以及超类。

特质背后的实现: Scala通过将 trait 翻译成 JVM 的类和接口关于通过反编译的方式查看 Scala 特质的背后工作方式可以参照 Scala 令人着迷的类设计中介绍的方法,有兴趣的可以看看.

 

9.总结Java接口 (interface) Scala特质 (trait) 

通过上面对Java接口和Scala特质的学习,我们发现它们之间有很多的相似性,同时也有差别。

9.1相似性:

Java接口和Scala特质都可以包含抽象方法和具体实现(Java8新增了default关键字,可以使接口有自己的默认的实现类,而且还不影响接口的实现类。)

ScalaJava一样都不允许类从多个超类继承,但分别可以叠加多个特质和实现多个接口;

9.2差异性:

Java只能在Class层面添加接口的实现,Scala可以在Class和对象层面混入特质。Scala通过在对象层面动态混入特质,相比而言具有更大的灵活性。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章