Kotlin數(shù)據(jù)類、密封類、嵌套類、內(nèi)部類、枚舉類、內(nèi)聯(lián)類

一、數(shù)據(jù)類

數(shù)據(jù)類標(biāo)記為 data,類似java POJO。

data class User(val name: String, val age: Int)

為了確保生成的代碼的?致性以及有意義的行為,數(shù)據(jù)類必須滿足以下要求:

  • 主構(gòu)造函數(shù)需要至少有?個(gè)參數(shù);
  • 主構(gòu)造函數(shù)的所有參數(shù)需要標(biāo)記為 val 或 var ;
  • 數(shù)據(jù)類不能是抽象、開放、密封或者內(nèi)部的;
  • 數(shù)據(jù)類只能實(shí)現(xiàn)接口。(在1.1之前,1.1之后可以擴(kuò)展其它類)

編譯器自動(dòng)從住構(gòu)造函數(shù)中聲明的所有屬性成員導(dǎo)出以下成員:

  • equals() / hashCode() 對
  • toString() 格式是 "User(name=John, age=42)"
  • componentN() 函數(shù) 按聲明順序?qū)?yīng)于所有屬性
  • copy() 函數(shù)

成員生成遵循關(guān)于成員繼承的這些規(guī)則:

  • 如果在數(shù)據(jù)類體中有顯式實(shí)現(xiàn) equals() 、hashCode() 或者 toString() ,或者這些函數(shù)在父類中有 final 實(shí)現(xiàn),那么不會生成這些函數(shù),而會使用現(xiàn)有函數(shù);
  • 如果超類型具有 open 的 componentN() 函數(shù)并且返回兼容的類型,那么會為數(shù)據(jù)類?成相應(yīng)的函數(shù),并 覆蓋超類的實(shí)現(xiàn)。如果超類型的這些函數(shù)由于簽名不兼容或者是 Mnal 而導(dǎo)致無法覆蓋,那么會報(bào)錯(cuò)。
  • 不允許從?個(gè)已具 copy(……) 函數(shù)且簽名匹配的類型派生?個(gè)數(shù)據(jù)類。
  • 不允許為 componentN() 以及 copy() 函數(shù)提供顯式實(shí)現(xiàn)。

1.在類體中聲明的屬性

自動(dòng)生成的函數(shù),編譯器只使用在主構(gòu)造函數(shù)內(nèi)部定義的屬性。如需在生成的實(shí)現(xiàn)中排除?個(gè)屬性,需要聲明在類體中。

2.復(fù)制

需要復(fù)制?個(gè)對象改變它的?些屬性,但其余部分保持不變

fun copy(name: String = this.name, age: Int = this.age) = User(name, age)

val jack = User(name = "Jack", age = 1) 
val olderJack = jack.copy(age = 2)

3.數(shù)據(jù)類與解構(gòu)聲明

為數(shù)據(jù)類生成的 Component 函數(shù) 使它們可在解構(gòu)聲明中使用:

val jane = User("Jane", 35) 
val (name, age) = jane 
println("$name, $age years of age") // 輸出 "Jane, 35 years of age"

4.標(biāo)準(zhǔn)數(shù)據(jù)類

標(biāo)準(zhǔn)庫提供了 Pair 與 Triple 。盡管在很多情況下具名數(shù)據(jù)類是更好的設(shè)計(jì)選擇,因?yàn)樗鼈兺ㄟ^為屬性提供有意義的名稱使代碼更具可讀性。

二、密封類

密封類用來表式受限的類繼承結(jié)構(gòu):當(dāng)?個(gè)值為有限幾種的類型、而不能有任何其他類型時(shí)。在某種意義上,是枚舉類的擴(kuò)展:枚舉類型的值集合也是受限的,但每個(gè)枚舉常量只存在?個(gè)實(shí)例,而密封類的?個(gè)子類可以有可包含狀態(tài)的多個(gè)實(shí)例。

  • 聲明一個(gè)密封類,在類名前?添加 sealed 修飾符。所有子類都必須在與密封類相同的文件中聲明。
  • ?個(gè)密封類是自身抽象的,它不能直接實(shí)例化,可以有抽象(abstract)成員。
  • 密封類不允許有非-private 構(gòu)造函數(shù)(其構(gòu)造函數(shù)默認(rèn)為 private)
  • 擴(kuò)展密封類子類的類(間接繼承者)可以放在任何位置,無需在同?個(gè)?件中
sealed class Expr 
data class Const(val number: Double) : Expr()   //數(shù)據(jù)類繼承密封類
data class Sum(val e1: Expr, val e2: Expr) : Expr() 
object NotANumber : Expr()

使?密封類的關(guān)鍵好處在于使用 when 表達(dá)式 的時(shí)候,如果能夠驗(yàn)證語句覆蓋了所有情況,就不需要為該語句再添加?個(gè) else 子句了。當(dāng)然,這只有當(dāng)你用 when 作為表達(dá)式而不是作為語句時(shí)才有用。

private fun sealedClassAndWhen(expr: Expr) = when (expr) {
    is Const -> expr.number
    is Sum -> eval(expr.e1) + eval(null)
    NotANumber -> Double.NaN // 不再需要 `else` ?句,因?yàn)槲覀円呀?jīng)覆蓋了所有的情況 }
}

三、嵌套類與內(nèi)部類

類可以嵌套在其他類中:

class Outer {
    private val bar:Int =1

    class Nested {
        fun foo() =2
    }
}
val demo = Outer.Nested().foo() // == 2,調(diào)用嵌套類方法和java內(nèi)部類一樣

1.內(nèi)部類

標(biāo)記為 inner 的嵌套類能夠訪問其外部類的成員。內(nèi)部類會帶有?個(gè)對外部類的對象的引用:

class Outer {
    private val bar: Int = 1 
    inner class Inner {
        fun foo() = bar
    }
}
val demo = Outer().Inner().foo() // == 1

2.匿名內(nèi)部類

使用對象表達(dá)式創(chuàng)建匿名內(nèi)部類實(shí)例:

window.addMouseListener(object : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) {
        ……
    }

    override fun mouseEntered(e: MouseEvent) {
        ……
    }
})

對象是函數(shù)式接口(當(dāng)個(gè)抽象方法的接口),可以使用拉姆達(dá)表達(dá)式進(jìn)行SAM轉(zhuǎn)換,使?帶接口類型前綴的lambda表達(dá)式創(chuàng)建。

val listener = ActionListener { println("clicked") }

四、枚舉類

枚舉類的最基本的用法是實(shí)現(xiàn)類型安全的枚舉。每個(gè)枚舉常量都是?個(gè)對象。枚舉常量用逗號分隔。

enum class Direction { 
    NORTH, SOUTH, WEST, EAST 
}

1.初始化

因?yàn)槊?個(gè)枚舉都是枚舉類的實(shí)例,所以他們可以是這樣初始化過的:

enum class Color(val rgb: Int) {
    RED(0xFF0000),
    GREEN(0x00FF00),
    BLUE(0x0000FF)
}

2.匿名類

枚舉常量還可以聲明其帶有相應(yīng)方法以及覆蓋了基類方法的匿名類

enum class ProtocolState {
    WAITING {
        override fun signal() = TALKING
    },
    TALKING {
        override fun signal() = WAITING
    };

    abstract fun signal(): ProtocolState
}

3.在枚舉類中實(shí)現(xiàn)接口

?個(gè)枚舉類可以實(shí)現(xiàn)接口(但不能從類繼承),可以為所有條目提供統(tǒng)?的接口成員實(shí)現(xiàn),也可以在相應(yīng)匿名類中為每個(gè)條目提供各自的實(shí)現(xiàn)。只需將接口添加到枚舉類聲明中即可。

enum class IntArithmetics : BinaryOperator<Int>, IntBinaryOperator {
    PLUS {
        override fun apply(t: Int, u: Int): Int = t + u
    },
    TIMES {
        override fun apply(t: Int, u: Int): Int = t * u
    };

    override fun applyAsInt(t: Int, u: Int) = apply(t, u)
}

4.使用枚舉常量

Kotlin中的枚舉類也有合成?法允許列出定義的枚舉常量以及通過名稱獲取枚舉常量:

EnumClass.valueOf(value: String): EnumClass 
EnumClass.values(): Array<EnumClass>

可以使用 enumValues<T>() 與 enumValueOf<T>() 函數(shù)以泛型的方式訪問枚舉類中的常量 :

enum class RGB { RED, GREEN, BLUE }

inline fun <reified T : Enum<T>> printAllValues() {
    print(enumValues<T>().joinToString { it.name })
}

printAllValues<RGB>()// 輸出 RED, GREEN, BLUE

每個(gè)枚舉常量都具有在枚舉類聲明中獲取其名稱與位置的屬性:

val name: String 
val ordinal: Int

枚舉常量還實(shí)現(xiàn)了 Comparable 接口,其中?然順序是它們在枚舉類中定義的順序。

五、內(nèi)聯(lián)類

圍繞某種類型創(chuàng)建包裝器,會由于額外的堆內(nèi)存分配問題,引入運(yùn)行時(shí)的性能開銷。此外,如果被包裝的類型是原生類型,性能的損失是很糟糕的,因?yàn)樵愋屯ǔT谶\(yùn)行時(shí)就進(jìn)行了大量優(yōu)化,然而他們的包裝器卻沒有得到任何特殊的處理。為了解決這類問題,Kotlin 引?了?種被稱為內(nèi)聯(lián)類的特殊類,它通過在類的前?定義?個(gè) inline 修飾符來聲明.
內(nèi)聯(lián)類必須含有唯?的?個(gè)屬性在主構(gòu)造函數(shù)中初始化。在運(yùn)行時(shí),將使用這個(gè)唯?屬性來表示內(nèi)聯(lián)類的實(shí)例。這就是內(nèi)聯(lián)類的主要特性,類的數(shù)據(jù)被 “內(nèi)聯(lián)”到該類使用的地方。

1.成員

內(nèi)聯(lián)類支持普通類中的?些功能。特別是,內(nèi)聯(lián)類可以聲明屬性與函數(shù):

inline class Name(val s: String) {
    val length: Int get() = s.length
    fun greet() {
        println("Hello, $s")
    }
}

fun main() {
    val name = Name("Kotlin")
    name.greet()            // `greet` ?法會作為?個(gè)靜態(tài)?法被調(diào)? 
    println(name.length)    // 屬性的 get ?法會作為?個(gè)靜態(tài)?法被調(diào)?
}

內(nèi)聯(lián)類的成員也有?些限制:

  • 內(nèi)聯(lián)類不能含有 init 代碼塊
  • 內(nèi)聯(lián)類不能含有幕后字段,所以內(nèi)聯(lián)類只能含有簡單的計(jì)算屬性(不能含有延遲初始化/委托屬性)

2.繼承

內(nèi)聯(lián)類允許去繼承接口。禁止內(nèi)聯(lián)類參與到類的繼承關(guān)系結(jié)構(gòu)中。這就意味著內(nèi)聯(lián)類不能繼承其他的類而且必須是 final。

3.表示方式

Kotlin 編譯器為每個(gè)內(nèi)聯(lián)類保留?個(gè)包裝器。內(nèi)聯(lián)類的實(shí)例可以在運(yùn)行時(shí)表示為包裝器或者基礎(chǔ)類型。這就類似于 Int 可以表示為原生類型 int 或者包裝器Integer。內(nèi)聯(lián)類既可以表式為基礎(chǔ)類型有可以表示為包裝器,引用相等對于內(nèi)聯(lián)類而言毫?意義,因此這也是被禁止的。

interface I
inline class Foo(val i: Int) : I

fun asInline(f: Foo) {}
fun <T> asGeneric(x: T) {}
fun asInterface(i: I) {}
fun asNullable(i: Foo?) {}
fun <T> id(x: T): T = x
fun main() {
    val f = Foo(42)
    asInline(f) // 拆箱操作: ?作 Foo 本?
    asGeneric(f) // 裝箱操作: ?作泛型類型 T
    asInterface(f) // 裝箱操作: ?作類型 I
    asNullable(f) // 裝箱操作: ?作不同于 Foo 的可空類型 Foo?
    // 在下?這?例?中,'f' ?先會被裝箱(當(dāng)它作為參數(shù)傳遞給 'id' 函數(shù)時(shí))然后?被拆箱(當(dāng)它從'id'函數(shù)中被返回 時(shí))
    // 最后, 'c' 中就包含了被拆箱后的內(nèi)部表達(dá)(也就是 '42'), 和 'f' ?樣
    val c = id(f)
}

4.名字修飾

于內(nèi)聯(lián)類被編譯為其基礎(chǔ)類型,因此可能會導(dǎo)致各種模糊的錯(cuò)誤,例如意想不到的平臺簽名沖突:

inline class UInt(val x: Int)

// 在 JVM 平臺上被表?為'public final void compute(int x)' 
fun compute(x: Int) {}

// 同理,在 JVM 平臺上也被表?為'public final void compute(int x)'! 
fun compute(x: UInt) {}

會通過在函數(shù)名后面拼接?些穩(wěn)定的哈希碼來重命名函數(shù)。因此,fun compute(x: UInt) 將會被表示為 public final void compute-<hashcode>(int x) ,以此來解決沖突的問題?!?””是?個(gè)無效的 符號,也就是說在 Java 中不能調(diào)用使用內(nèi)聯(lián)類作為形參的函數(shù)。

5.內(nèi)聯(lián)類與類型別名

內(nèi)聯(lián)類似乎與類型別名非常相似。關(guān)鍵的區(qū)別在于類型別名與其基礎(chǔ)類型(以及具有相同基礎(chǔ)類型的其他類型別名)是賦值兼容的,而內(nèi)聯(lián)類卻不是這樣。 換句話說,內(nèi)聯(lián)類引入了?個(gè)真實(shí)的新類型,與類型別名正好相反,類型別名僅僅是為現(xiàn)有的類型取了個(gè)新的替代名稱(別名):

typealias NameTypeAlias = String

inline class NameInlineClass(val s: String)

fun acceptString(s: String) {}
fun acceptNameTypeAlias(n: NameTypeAlias) {}
fun acceptNameInlineClass(p: NameInlineClass) {}
fun main() {
    val nameAlias: NameTypeAlias = ""
    val nameInlineClass: NameInlineClass = NameInlineClass("")
    val string: String = ""
    acceptString(nameAlias)         // 正確: 傳遞別名類型的實(shí)參替代函數(shù)中基礎(chǔ)類型的形參
    acceptString(nameInlineClass)   // 錯(cuò)誤: 不能傳遞內(nèi)聯(lián)類的實(shí)參替代函數(shù)中基礎(chǔ)類型的形參 
    // And vice versa: 
    acceptNameTypeAlias(string)     // 正確: 傳遞基礎(chǔ)類型的實(shí)參替代函數(shù)中別名類型的形參 
    acceptNameInlineClass(string)   // 錯(cuò)誤: 不能傳遞基礎(chǔ)類型的實(shí)參替代函數(shù)中內(nèi)聯(lián)類類型的形參 
}

6.內(nèi)聯(lián)類的 alpha 狀態(tài)

處于alpha狀態(tài),使用會有警告,必須通過指定編譯器參數(shù) -Xinline-classes 來選擇使用這項(xiàng)特性。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容