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通過在對象層面動態混入特質,相比而言具有更大的靈活性。


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