Java & Groovy & Scala & Kotlin - 23.Trait

Overview

Trait 中文名為特質(zhì)消恍。特質(zhì)是字段和行為的集合惰匙,可以擁有抽象成員也可以擁有普通成員。特質(zhì)可以看做是一種特殊形式的接口署恍,但是特質(zhì)主要用于實現(xiàn)多重繼承。

多重繼承雖然便利蜻直,但是會帶來 Diamond Problem盯质,即 B 和 C 都實現(xiàn)了 A 的某個方法,而 D 繼承了 B 和 C 但是沒有重寫該方法概而,此時調(diào)用 D 持有的該方法到底來自于 B 還是 C呼巷。因此過分使用特質(zhì)會讓程序本身難以理解。

Java 篇

Java 誕生時就倡導語法本身應(yīng)該簡單容易學習赎瑰,所以設(shè)計時 Java 就只支持單繼承王悍,因此 Java 并不支持特質(zhì),不過可以通過內(nèi)部類實現(xiàn)類似多重繼承的功能餐曼。

Groovy 篇

創(chuàng)建特質(zhì)

特質(zhì)使用關(guān)鍵字 trait 聲明压储,可以擁有普通成員和抽象成員。

例:

trait MessageHandler {
    //  屬性
    int minLenght

    //  方法
    //  普通方法
    void echo(String msg) {
        println(msg)
    }

    //  抽象方法
    abstract void show(String msg)
}

Groovy 中特質(zhì)本質(zhì)上是運行時對接口的實現(xiàn)源譬,所以其方法的訪問控制符只支持 publicprivate集惋。

使用特質(zhì)

特質(zhì)就像接口一樣使用關(guān)鍵字 implements 進行實現(xiàn)。

例:

class DefaultMessageHandler implements MessageHandler {

    @Override
    void show(String msg) {
        println(msg)
    }
}

def handler = new DefaultMessageHandler()
handler.show("foo")

實現(xiàn)接口與特質(zhì)

特質(zhì)可以實現(xiàn)接口踩娘。

例:

interface Named {
    String name()
}
trait Greetable implements Named {
    String greeting() { "Hello, ${name()}!" }
}

特質(zhì)也可以實現(xiàn)其它特質(zhì)芋膘。

例:

trait OutputLogger implements Logger{
    @Override
    void log(String msg) {
        super.log(msg)
    }
}

多重繼承時使用 , 作為分隔符,這點和接口一樣

例:

class Duck implements FlyingAbility, SpeakingAbility {}

特質(zhì)實現(xiàn)接口和特質(zhì)使用的是關(guān)鍵字 implements霸饲,而接口實現(xiàn)接口使用的則是關(guān)鍵字 extends为朋。

帶有特質(zhì)的對象

使用 withTraits 可以動態(tài)通過對象實現(xiàn)特質(zhì)而不用在類上定義特質(zhì)。

例:

def logger2 = new BasicLogger().withTraits(OutputLogger)
logger2.log("hello world")

withTraits 中可以包含多個特質(zhì)厚脉,每個特質(zhì)以 , 分隔习寸。

Diamond Problems

對于 Diamond Problems,Groovy 采用的是最右邊定義的贏的規(guī)則傻工。即一個類實現(xiàn)了多個包含同名方法的特質(zhì)時霞溪,總是調(diào)用最右邊的特質(zhì)定義的方法孵滞。

定義特質(zhì) A,B 和實現(xiàn)特質(zhì)的類 C

trait A {
    void echo() {
        println("A")
    }
}

trait B {
    void echo() {
        println("B")
    }
}

class C {}

調(diào)用 C

def c1 = new C().withTraits(A, B)
c1.echo()   //  B
def c2 = new C().withTraits(B, A)
c2.echo()   //  A

鏈式操作

鏈式操作即實現(xiàn)多個特質(zhì)時,每個特質(zhì)可以通過 super 調(diào)用上一個特質(zhì)的實現(xiàn)鸯匹。特質(zhì)的鏈式操作可以說是一個比較復雜的概念坊饶,建議使用 Debug 進行跟蹤來進行理解。簡單來說就是基于 "最右為贏" 的原則殴蓬,總是調(diào)用最右邊的特質(zhì)匿级,在該特質(zhì)調(diào)用 super 后就調(diào)用其左邊的特質(zhì)的實現(xiàn)。

以下是一個鏈式操作的完整的例子

定義一個 Logger 接口

interface Logger {
    void log(String msg)
}

鏈式操作時最上層定義的必須為接口染厅,而不能是擁有抽象類的特質(zhì) trait Logger {abstract void log(String msg}布蔗。

定義實現(xiàn)該接口的幾個特質(zhì)

trait OutputLogger implements Logger {

    @Override
    void log(String msg) {
        println("--> Seeing msg in OutputLogger.")
        println(msg)
    }
}

trait ShortLogger implements Logger {

    final int maxLength = 15

    @Override
    void log(String msg) {
        println("--> Seeing msg in ShortLogger.")
        if (msg.length() <= maxLength)
            super.log(msg)
        else
            super.log(msg.substring(0, maxLength - 3) + "...")

    }
}

trait TimeStampLogger implements Logger {
    @Override
    void log(String msg) {
        println("--> Seeing msg in TimeStampLogger.")
        super.log(new Date().toString() + " " + msg)
    }

}

以上定義了三個特質(zhì)入问,其中 OutputLogger 用于執(zhí)行打印操作,ShortLogger 限制傳入的消息長度為15,TimeStampLogger 用于在消息前追加當前時間骨杂。

執(zhí)行鏈式操作

public class BasicLogger {
}

def loggerX = new BasicLogger().withTraits(OutputLogger, TimeStampLogger, ShortLogger)
loggerX.log("hello world loggerX")  //  Mon Oct 05 12:01:49 CST 2015 hello world ...

def loggerY = new BasicLogger().withTraits(OutputLogger, ShortLogger, TimeStampLogger)
loggerY.log("hello world loggerY")  //  Mon Oct 05 1...

以上 loggerX 先進行了截取操作再追加時間虾攻,而 loggerY 先追加時間再進行截取又官。

Scala 篇

創(chuàng)建特質(zhì)

Scala 中特質(zhì)也使用關(guān)鍵字 trait 聲明烫沙,可以擁有普通成員和抽象成員。

例:

trait MessageHandler {
    //  屬性
    val minLength: Int

    //  方法
    //  普通方法
    def echo(msg: String) {
        println(msg)
    }

    //  抽象方法
    def show(msg: String)
}

使用特質(zhì)

Scala 中只實現(xiàn)一個特質(zhì)時使用關(guān)鍵字 extends 魂那。

例:

class DefaultMessageHandler extends MessageHandler {
    override val minLength: Int = 100
    override def show(msg: String): Unit = {
        println(msg)
    }
}

val handler = new DefaultMessageHandler
handler.show("foo")

實現(xiàn)特質(zhì)

一個特質(zhì)可以實現(xiàn)其它特質(zhì)蛾号。

例:

trait OutputLogger extends Logger {
    override def log(msg: String): Unit = println(msg)
}

實現(xiàn)多個特質(zhì)時使用 with 作為分隔符

例:

class Duck extends FlyingAbility with SpeakingAbility {}

帶有特質(zhì)的對象

Scala 中也可以使用關(guān)鍵字 with 來動態(tài)通過對象實現(xiàn)特質(zhì)而不用在類上定義特質(zhì)。

例:

val logger = new BasicLogger with OutputLogger
logger.log("hello world")

Diamond Problems

對于 Diamond Problems冰寻,Scala 采用的方式非常簡單须教,類必須重寫造成 Diamond Problems 的方法,如果不重寫的會報運行時異常 "Inherits conflicting members"斩芭。

特質(zhì)的構(gòu)造順序

Scala 中特質(zhì)構(gòu)造順序遵循以下原則

  • 首先調(diào)用超類的構(gòu)造器
  • 特質(zhì)構(gòu)造器在超類構(gòu)造器之后轻腺,類構(gòu)造器之前執(zhí)行
  • 特質(zhì)由左至右被構(gòu)造
  • 每個特質(zhì)中,父特質(zhì)先被構(gòu)造
  • 如果多個特質(zhì)有同一個父特質(zhì)划乖,且父特質(zhì)已被構(gòu)造了贬养,則不會被再次構(gòu)造
  • 所有特質(zhì)構(gòu)造完畢,子類才被構(gòu)造

例:

有如下類

trait Logger
trait FileLogger extends Logger
trait ShortLogger extends Logger
class Account
class SavingAccount extends Account with FileLogger with ShortLogger

則 SavingAccount 的構(gòu)造順序為

  1. Account
  2. Logger
  3. FileLogger
  4. ShortLogger
  5. SavingAccount

鏈式操作

Scala 中雖然沒有 Groovy 的最右為贏的原則琴庵,但是同樣的 Scala 中右邊的特質(zhì)調(diào)用 super 后會調(diào)用其左邊的特質(zhì)的實現(xiàn)误算。

以下是一個鏈式操作的完整的例子

定義一個 Logger 接口

trait Logger {
    def log(msg: String)
}

注意 Groovy 這里使用的是接口,而 Scala 為特質(zhì)

定義實現(xiàn)該接口的幾個特質(zhì)

trait OutputLogger extends Logger {
    override def log(msg: String): Unit = println(msg)
}

trait ShortLogger extends Logger {
    val maxLength = 15

    override def log(msg: String): Unit = {
        super.log(
            if (msg.length <= maxLength) msg else msg.substring(0, maxLength - 3) + "..."
        )
    }
}

trait TimeStampLogger extends Logger {
    override def log(msg: String): Unit = {
        super.log(new Date() + " " + msg)
    }
}

以上定義了三個特質(zhì)迷殿,其中 OutputLogger 用于執(zhí)行打印操作儿礼,ShortLogger 限制傳入的消息長度為15,TimeStampLogger 用于在消息前追加當前時間庆寺。

執(zhí)行鏈式操作

class BasicLogger {
}

val loggerX = new BasicLogger with OutputLogger with TimeStampLogger with ShortLogger
loggerX.log("hello world loggerX") //   Mon Feb 16 11:46:06 CST 2015 hello world ...
val loggerY = new BasicLogger with OutputLogger with ShortLogger with TimeStampLogger
loggerY.log("hello world loggerY") //   Mon Feb 16 1...

以上 loggerX 先進行了截取操作再追加時間蚊夫,而 loggerY 先追加時間再進行截取。

Kotlin 篇

Kotlin 曾經(jīng)支持過特質(zhì)懦尝,不過后來由于多重繼承等特性會把程序搞得過于復雜知纷,而且也不是沒有其它解決方案壤圃,所以 Kotlin 目前的版本已經(jīng)將特質(zhì)廢棄了。

思考

什么時候應(yīng)該使用特質(zhì)而不是抽象類琅轧?

如果你想定義一個類似接口的類型伍绳,你可能會在特質(zhì)和抽象類之間難以取舍。這兩種形式都可以讓你定義一個類型的一些行為乍桂,并要求繼承者定義一些其他行為冲杀。一些經(jīng)驗法則:

  • 優(yōu)先使用特質(zhì)。一個類擴展多個特質(zhì)是很方便的模蜡,但卻只能擴展一個抽象類漠趁。
  • 如果你需要構(gòu)造函數(shù)參數(shù)扁凛,使用抽象類忍疾。因為抽象類可以定義帶參數(shù)的構(gòu)造函數(shù),而特質(zhì)不行谨朝。
  • 如果需要使用的類是從 Java類繼承過來的卤妒,使用抽象類。
  • 如果需要考慮效率問題字币,使用抽象類则披。Java的動態(tài)綁定機制決定了直接方法要快于接口方法。而特質(zhì)最終是被編譯成接口洗出。

Summary

  • 特質(zhì)最終會被編譯成接口
  • 只有 Groovy 和 Scala 支持特質(zhì)
  • 對于 Diamond 問題士复,Groovy 的原則是最右為贏,Scala 的原則是必須重寫

文章源碼見 https://github.com/SidneyXu/JGSK 倉庫的 _23_trait 小節(jié)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末翩活,一起剝皮案震驚了整個濱河市阱洪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌菠镇,老刑警劉巖冗荸,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異利耍,居然都是意外死亡蚌本,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進店門隘梨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來程癌,“玉大人,你說我怎么就攤上這事轴猎∏独颍” “怎么了?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵税稼,是天一觀的道長烦秩。 經(jīng)常有香客問我垮斯,道長,這世上最難降的妖魔是什么只祠? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任兜蠕,我火速辦了婚禮,結(jié)果婚禮上抛寝,老公的妹妹穿的比我還像新娘熊杨。我一直安慰自己,他們只是感情好盗舰,可當我...
    茶點故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布晶府。 她就那樣靜靜地躺著,像睡著了一般钻趋。 火紅的嫁衣襯著肌膚如雪川陆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天蛮位,我揣著相機與錄音较沪,去河邊找鬼。 笑死失仁,一個胖子當著我的面吹牛尸曼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播萄焦,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼控轿,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了拂封?” 一聲冷哼從身側(cè)響起茬射,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎烘苹,沒想到半個月后躲株,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡镣衡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年霜定,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片廊鸥。...
    茶點故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡望浩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出惰说,到底是詐尸還是另有隱情磨德,我是刑警寧澤,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站典挑,受9級特大地震影響酥宴,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜您觉,卻給世界環(huán)境...
    茶點故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一拙寡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧琳水,春花似錦肆糕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至私沮,卻和暖如春始赎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背顾彰。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工极阅, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留胃碾,地道東北人涨享。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像仆百,于是被迫代替她去往敵國和親厕隧。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,630評論 2 359

推薦閱讀更多精彩內(nèi)容