一、數(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)特性。