kotlin類與對象——>數(shù)據(jù)類、密封類、泛型
數(shù)據(jù)類,用來保存數(shù)據(jù)的類,使用data對class進(jìn)行標(biāo)記
data class User(val name: String, val age: Int) //編譯器自動從主構(gòu)造函數(shù)中聲明的所有屬性導(dǎo)出以下成員: //— equals() / hashCode() 對; //— toString() 格式是 "User(name=John, age=42)" ; //— componentN() 函數(shù) 按聲明順序?qū)?yīng)于所有屬性; //— copy() 函數(shù)(?下文)。 //為了確保生成的代碼的一致性以及有意義的行為,數(shù)據(jù)類必須滿足以下要求: //— 主構(gòu)造函數(shù)需要至少有一個參數(shù); //— 主構(gòu)造函數(shù)的所有參數(shù)需要標(biāo)記為 val 或 var ; //— 數(shù)據(jù)類不能是抽象、開放、密封或者內(nèi)部的; — (在1.1之前)數(shù)據(jù)類只能實現(xiàn)接口。 //此外,成員生成遵循關(guān)于成員繼承的這些規(guī)則: //— 如果在數(shù)據(jù)類體中有顯式實現(xiàn) equals()、hashCode() 或者 toString(),或者這些函數(shù)在 //父類中有 final 實現(xiàn),那么不會生成這些函數(shù),而會使用現(xiàn)有函數(shù); //— 如果超類型具有 open 的 componentN() 函數(shù)并且返回兼容的類型,那么會為數(shù)據(jù)類生成相應(yīng)的 //函數(shù),并覆蓋超類的實現(xiàn)。如果超類型的這些函數(shù)由于簽名不兼容或者是 final 而導(dǎo)致無法覆蓋,那 么會報錯; //— 從一個已具 copy(......) 函數(shù)且簽名匹配的類型派生一個數(shù)據(jù)類在 Kotlin 1.2 中已棄用,并且在 Kotlin 1.3 中已禁用。 //— 不允許為 componentN() 以及 copy() 函數(shù)提供顯式實現(xiàn)。 自 1.1 起,數(shù)據(jù)類可以擴(kuò)展其他類(示例請參?密封類)。 //在 JVM 中,如果生成的類需要含有一個無參的構(gòu)造函數(shù),則所有的屬性必須指定默認(rèn)值。(參?構(gòu)造函 數(shù))。 data class User(val name: String = "", val age: Int = 0)
2.在類體中聲明的屬性,對于自動生成的函數(shù),編譯器只使用在主構(gòu)造函數(shù)內(nèi)部定義的屬性,如需在生成的實現(xiàn)中排除一個屬性,請將其聲明在類體中:
data class Person(val name: String) { var age: Int = 0 } //在 toString() 、equals() 、hashCode() 以及 copy() 的實現(xiàn)中只會用到 name 屬性 //并且 只有一個 component 函數(shù) component1() 。雖然兩個 Person 對象可以有不同的年齡,但它們會視 為相等 val person1 = Person("John") val person2 = Person("John") person1.age = 10 person2.age = 20
3.復(fù)制,在很多情況下,需要復(fù)制一個對象改變它的一些屬性,其他部分保持不變,copy() 函數(shù)就是為 此而生成。對于上文的 User 類,其實現(xiàn)會類似下面這樣:
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)
4.數(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"
5.標(biāo)準(zhǔn)數(shù)據(jù)類,標(biāo)準(zhǔn)庫提供了 Pair 與 Triple 。盡管在很多情況下具名數(shù)據(jù)類是更好的設(shè)計選擇,因為它們通過為 屬性提供有意義的名稱使代碼更具可讀性
6.密封類,密封類用來表示受限的類繼承結(jié)構(gòu):當(dāng)一個值為有限幾種的類型、而不能有任何其他類型時。在某種意 義上,他們是枚舉類的擴(kuò)展:枚舉類型的值集合也是受限的,但每個枚舉常量只存在一個實例,而密封 類的一個子類可以有可包含狀態(tài)的多個實例
6.1 要聲明一個密封類,需要在類名前面添加 sealed 修飾符。雖然密封類也可以有子類,但是所有子類都 必須在與密封類自身相同的文件中聲明。(在 Kotlin 1.1 之前,該規(guī)則更加嚴(yán)格:子類必須嵌套在密封類聲明的內(nèi)部)。
sealed class Expr data class Const(val number: Double) : Expr() data class Sum(val e1: Expr, val e2: Expr) : Expr() object NotANumber : Expr()
//上文示例使用了Kotlin1.1的一個額外的新功能:數(shù)據(jù)類擴(kuò)展包括密封類在內(nèi)的其他類的可能性。)
6.2 一個密封類是自身抽象的,它不能直接實例化并可以有抽象(abstract)成員。密封類不允許有非-private 構(gòu)造函數(shù)(其構(gòu)造函數(shù)默認(rèn)為 private)。 請注意,擴(kuò)展密封類子類的類(間接繼承者)可以放在任何位置,而無需在同一個文件中。使用密封類的關(guān)鍵好處在于使用 when 表達(dá)式 的時候,如果能夠驗證語句覆蓋了所有情況,就不需要為 該語句再添加一個 else 子句了。當(dāng)然,這只有當(dāng)你用 when 作為表達(dá)式(使用結(jié)果)而不是作為語句 時才有用。
fun eval(expr: Expr): Double = when(expr) { is Const -> expr.number is Sum -> eval(expr.e1) + eval(expr.e2) NotANumber -> Double.NaN // 不再需要 `else` 子句,因為我們已經(jīng)覆蓋了所有的情況 }
7.泛型,與 Java 類似,Kotlin 中的類也可以有類型參數(shù)
class Box<T>(t: T) { var value = t } //一般來說,要創(chuàng)建這樣類的實例,我們需要提供類型參數(shù): val box: Box<Int> = Box<Int>(1) //但是如果類型參數(shù)可以推斷出來,例如從構(gòu)造函數(shù)的參數(shù)或者從其他途徑,允許省略類型參數(shù): val box = Box(1) // 1 具有類型 Int,所以編譯器知道我們說的是 Box<Int>。
8.型變,在java系統(tǒng)中有通配符類型,而kotlin中沒有,而換成兩個其他的東西:聲明處型變(declaration-site variance)與類型投影(type projections)
8.1 聲明處型變
//假設(shè)有一個泛型接口 Source<T>,該接口中不存在任何以 T 作為參數(shù)的方法,只是方法返回 T 類型值 // Java interface Source<T> { T nextT(); } //那么,在 Source <Object> 類型的變量中存儲 Source <String> 實例的引用是極為安全的? 沒有消費者-方法可以調(diào)用。 //但是 Java 并不知道這一點,并且仍然禁止這樣操作 // Java void demo(Source<String> strs) { Source<Object> objects = strs; // !!!在 Java 中不允許 // ...... }
為了修正這一點,我們必須聲明對象的類型為 Source<? extends Object>,這是毫無意義的,因 為我們可以像以前一樣在該對象上調(diào)用所有相同的方法,所以更復(fù)雜的類型并沒有帶來價值。但編譯器并不知道
在 Kotlin 中,有一種方法向編譯器解釋這種情況。這稱為聲明處型變:我們可以標(biāo)注 Source 的類型參數(shù) T 來確保它僅從 Source<T> 成員中返回(生產(chǎn)),并從不被消費。為此,我們提供 out 修飾符
interface Source<out T> { fun nextT(): T } fun demo(strs: Source<String>) { val objects: Source<Any> = strs // 這個沒問題,因為 T 是一個 out-參數(shù) // ...... }
一般原則是:當(dāng)一個類 C 的類型參數(shù) T 被聲明為 out 時,它就只能出現(xiàn)在 C 的成員的輸出-位置,但 回報是 C<Base> 可以安全地作為 C<Derived> 的超類。
簡而言之,他們說類 C 是在參數(shù) T 上是協(xié)變的,或者說 T 是一個協(xié)變的類型參數(shù)。你可以認(rèn)為 C 是 T 的生產(chǎn)者,而不是 T 的消費者。
out修飾符稱為型變注解,并且由于它在類型參數(shù)聲明處提供,所以我們稱之為聲明處型變。這與 Java 的使用處型變相反,其類型用途通配符使得類型協(xié)變。
另外除了 out,Kotlin 又補充了一個型變注釋:in。它使得一個類型參數(shù)逆變:只可以被消費而不可以被 生產(chǎn)。逆變類型的一個很好的例子是 Comparable :
interface Comparable<in T> { operator fun compareTo(other: T): Int } fun demo(x: Comparable<Number>) { x.compareTo(1.0) // 1.0 擁有類型 Double,它是 Number 的子類型 // 因此,我們可以將 x 賦給類型為 Comparable <Double> 的變量 val y: Comparable<Double> = x // OK! }
我們相信 in 和 out 兩詞是自解釋的(因為它們已經(jīng)在 C# 中成功使用很?時間了),因此上面提到的助 記符不是真正需要的,并且可以將其改寫為更高的目標(biāo)
8.2 類型投影,使用處型變:類型投影
將類型參數(shù) T 聲明為 out 非常方便,并且能避免使用處子類型化的麻煩,但是有些類實際上不能限制為只返回 T !一個很好的例子是 Array:
class Array<T>(val size: Int) { fun get(index: Int): T { ...... } fun set(index: Int, value: T) { ...... } } //該類在 T 上既不能是協(xié)變的也不能是逆變的。這造成了一些不靈活性。考慮下述函數(shù) fun copy(from: Array<Any>, to: Array<Any>) { assert(from.size == to.size) for (i in from.indices) to[i] = from[i] } //這個函數(shù)應(yīng)該將項目從一個數(shù)組復(fù)制到另一個數(shù)組。讓我們嘗試在實踐中應(yīng)用它 val ints: Array<Int> = arrayOf(1, 2, 3) val any = Array<Any>(3) { "" } copy(ints, any) // ^ 其類型為 Array<Int> 但此處期望 Array<Any>
這里我們遇到同樣熟悉的問題:Array <T> 在 T 上是不型變的,因此 Array <Int> 和 Array <Any> 都不是另一個的子類型。為什么?再次重復(fù),因為 copy 可能做壞事,也就是說,例如它可能嘗 試寫一個String到 from,并且如果我們實際上傳遞一個 Int 的數(shù)組,一段時間后將會拋出一個ClassCastException 異常。
//那么,我們唯一要確保的是 copy() 不會做任何壞事。我們想阻止它寫到 from,我們可以 fun copy(from: Array<out Any>, to: Array<Any>) { ...... }
這里發(fā)生的事情稱為類型投影:我們說 from 不僅僅是一個數(shù)組,而是一個受限制的(投影的)數(shù)組:我們 只可以調(diào)用返回類型為類型參數(shù) T 的方法,如上,這意味著我們只能調(diào)用 get() 。這就是我們的使用 處型變的用法,并且是對應(yīng)于 Java 的 Array<? extends Object> 、但使用更簡單些的方式。
//你也可以使用 in 投影一個類型: fun fill(dest: Array<in String>, value: String) { ...... }
Array<in String> 對應(yīng)于Java的 Array<? super String>,也就是說,你可以傳遞一個 CharSequence 數(shù)組或一個 Object 數(shù)組給 fill() 函數(shù)
8.3 星投影,有時你想說,你對類型參數(shù)一無所知,但仍然希望以安全的方式使用它。這里的安全方式是定義泛型類 型的這種投影,該泛型類型的每個具體實例化將是該投影的子類型,Kotlin 為此提供了所謂的星投影語法
— 對于 Foo <out T : TUpper> ,其中 T 是一個具有上界 TUpper 的協(xié)變類型參數(shù),F(xiàn)oo <*> 等價于 Foo <out TUpper> 。這意味著當(dāng) T 未知時,你可以安全地從 Foo <*> 讀取 TUpper 的值。 — 對于 Foo <in T> ,其中 T 是一個逆變類型參數(shù),F(xiàn)oo <*> 等價于 Foo <in Nothing> 。這 意味著當(dāng) T 未知時,沒有什么可以以安全的方式寫入 Foo <*> 。 — 對于 Foo <T : TUpper> ,其中 T 是一個具有上界 TUpper 的不型變類型參數(shù),F(xiàn)oo<*> 對于 讀取值時等價于 Foo<out TUpper> 而對于寫值時等價于 Foo<in Nothing> 。 如果泛型類型具有多個類型參數(shù),則每個類型參數(shù)都可以單獨投影。例如,如果類型被聲明為 interface Function <in T, out U>,我們可以想象以下星投影: — Function<*, String> 表示 Function<in Nothing, String> ; — Function<Int, *> 表示 Function<Int, out Any?> ; — Function<*, *> 表示 Function<in Nothing, out Any?> 。 注意:星投影非常像 Java 的原始類型,但是安全。
9.泛型函數(shù)
//不僅類可以有類型參數(shù)。函數(shù)也可以有。類型參數(shù)要放在函數(shù)名稱之前: fun <T> singletonList(item: T): List<T> { // ...... } fun <T> T.basicToString(): String { // 擴(kuò)展函數(shù) // ...... } //要調(diào)用泛型函數(shù),在調(diào)用處函數(shù)名之后指定類型參數(shù)即可: val l = singletonList<Int>(1) //可以省略能夠從上下文中推斷出來的類型參數(shù),所以以下示例同樣適用 val l = singletonList(1)
10. 泛型約束,能夠替換給定類型參數(shù)的所有可能類型的集合可以由泛型約束限制。
11. 上界
//最常?的約束類型是與 Java 的 extends 關(guān)鍵字對應(yīng)的 上界 fun <T : Comparable<T>> sort(list: List<T>) { ...... } //冒號之后指定的類型是上界:只有 Comparable<T> 的子類型可以替代 T 。例如: sort(listOf(1, 2, 3)) // OK。Int 是 Comparable<Int> 的子類型 sort(listOf(HashMap<Int, String>())) // 錯誤:HashMap<Int, String>不是 Comparable<HashMap<Int, String>> 的子類型 //默認(rèn)的上界(如果沒有聲明)是 Any? 。在尖括號中只能指定一個上界。如果同一類型參數(shù)需要多個上界,我們需要一個單獨的 where-子句 fun <T> copyWhenGreater(list: List<T>, threshold: T): List<String> where T : CharSequence, T : Comparable<T> { return list.filter { it > threshold }.map { it.toString() } } //所傳遞的類型必須同時滿足 where 子句的所有條件。在上述示例中,類型 T 必須既實現(xiàn)了 CharSequence 也實現(xiàn)了 Comparable
12.類型擦除,
Kotlin 為泛型聲明用法執(zhí)行的類型安全檢測僅在編譯期進(jìn)行。運行時泛型類型的實例不保留關(guān)于其類 型實參的任何信息。其類型信息稱為被擦除。例如,F(xiàn)oo<Bar> 與 Foo<Baz?> 的實例都會被擦除為Foo<*> 。
因此,并沒有通用的方法在運行時檢測一個泛型類型的實例是否通過指定類型參數(shù)所創(chuàng)建 ,并且編譯器禁止這種 is 檢測。
類型轉(zhuǎn)換為帶有具體類型參數(shù)的泛型類型,如 foo as List<String> 無法在運行時檢測。當(dāng)高級程序邏輯隱含了類型轉(zhuǎn)換的類型安全而無法直接通過編譯器推斷時,可以使用這種非受檢類型轉(zhuǎn)換。編 譯器會對非受檢類型轉(zhuǎn)換發(fā)出警告,并且在運行時只對非泛型部分檢測(相當(dāng)于 foo as List<*> )。
泛型函數(shù)調(diào)用的類型參數(shù)也同樣只在編譯期檢測。在函數(shù)體內(nèi)部,類型參數(shù)不能用于類型檢測,并且類 型轉(zhuǎn)換為類型參數(shù)(foo as T)也是非受檢的。然而,內(nèi)聯(lián)函數(shù)的具體化的類型參數(shù)會由調(diào)用處內(nèi)聯(lián)函數(shù)體中的類型實參所代入,因此可以用于類型檢測與轉(zhuǎn)換,與上述泛型類型的實例具有相同限制。

浙公網(wǎng)安備 33010602011771號