前言
本文章只是用于記錄學(xué)習(xí),所以部分地方如果有錯誤或者理解不對的地方硼被,麻煩請指正示损。本篇為 csdn 原文章 轉(zhuǎn)移修改版 原文章
簡述:
- kotlin 中數(shù)據(jù)類的聲明及條件
- kotlin 中 密封類/ 枚舉
- kotlin 中類型判斷
- kotlin 中泛型 和 數(shù)據(jù)協(xié)變
- kotlin 中的類型投射
1. 數(shù)據(jù)類
??在java 中我們通常會創(chuàng)建很多 Bean 類來存儲 數(shù)據(jù),在kotlin 中有專門的數(shù)據(jù)類嚷硫,“data”
data class User(val name: String, val age: Int)
數(shù)據(jù)類必須滿足幾個條件
- 主構(gòu)造函數(shù)需要至少有一個參數(shù)检访;
- 主構(gòu)造函數(shù)的所有參數(shù)需要標(biāo)記為 val 或 var始鱼;
- 數(shù)據(jù)類不能是抽象、開放脆贵、密封或者內(nèi)部的医清;
數(shù)據(jù)類 也為我們自動生成了部分代碼:
- equals()/hashCode() 對;
- toString() 格式是 "User(name=John, age=42)"丹禀;
- componentN() 函數(shù) 按聲明順序?qū)?yīng)于所有屬性状勤;
- copy() 函數(shù)
??在 JVM 中,如果生成的類需要含有一個無參的構(gòu)造函數(shù)双泪,則所有的屬性必須指定默認(rèn)值持搜。
data class User(val name: String = "", val age: Int = 0)
??下面我們使用代碼簡單聯(lián)系一下
data class User(val name: String, val age: Int)
fun main(args: Array<String>) {
var json = User(name = "ymc",age = 1)
val json1 = json.copy(age = 2)
println(json1) // 默認(rèn)調(diào)用 User的 tostring()
}
?? 在java 中我們通常想要賦值一個值,但是只需要改變某一項的值的數(shù)據(jù)信息焙矛,kotlin 中的 copy函數(shù)葫盼,只需要傳入不一樣的數(shù)據(jù),就會自動化改變村斟,并返回給你修改后的所有數(shù)據(jù)信息贫导。
2.密封類
??密封類,可以理解為枚舉蟆盹,規(guī)定了有限個類型孩灯,不可以存在其他類型,但枚舉每個枚舉常量只存在一個示例逾滥,但是密封類的子類可以有多個示例峰档,所以可以將密封類看做是枚舉的拓展,基于枚舉寨昙,高于枚舉讥巡,青出于藍(lán)而勝于藍(lán)。
??聲明一個密封類舔哪,需要在類名前面添加 sealed 修飾符欢顷。雖然密封類也可以有子類,但是所有子類都必須在與密封類自身相同的文件中聲明捉蚤。eg:
// 密封類
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
??相對于密封類的子類 必須要在一個文件中抬驴,擴展密封類子類的類(間接繼承者)可以放在任何位置,而無需在同一個文件中缆巧。
密封類注意點:
1.一個密封類是自身抽象的怎爵,它不能直接實例化 ,但是可以有抽象(abstract)成員盅蝗。
2.密封類不允許有非-private 構(gòu)造函數(shù)(其構(gòu)造函數(shù)默認(rèn)為 private)。
??使用密封類的關(guān)鍵好處在于使用 when 表達式 的時候姆蘸,如果能夠驗證語句覆蓋了所有情況墩莫,就不需要為該語句再添加一個 else 子句了芙委。
fun eval(expr: Expr): Double{
return when(expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
}
}
2. 類型檢測以及自動類型轉(zhuǎn)換
我們可以使用 is 運算符檢測一個表達式是否某類型的一個實例(類似于Java中的instanceof關(guān)鍵字)。
fun getStringLength(obj: Any): Int? {
if (obj is String) {
// 做過類型判斷以后狂秦,obj會被系統(tǒng)自動轉(zhuǎn)換為String類型
return obj.length
}
//在這里還有一種方法灌侣,與Java中instanceof不同,使用!is
// if (obj !is String){
// // XXX
// }
// 這里的obj仍然是Any類型的引用
return null
}
或者 可以再運算式中 使用
fun getStringLength(obj: Any): Int? {
// 在 `&&` 運算符的右側(cè), `obj` 的類型會被自動轉(zhuǎn)換為 `String`
if (obj is String && obj.length > 0)
return obj.length
return null
}
3. 泛型
??kotlin中的泛型和 java 中的差不多裂问,但是很多方面更加簡潔侧啼。
// kotlin中的 泛型
class Box<T>(t: T) {
var value = t
}
如果我們要創(chuàng)建
val box: Box<Int> = Box<Int>(1)
// 1 具有類型 Int,所以編譯器知道我們說的是 Box<Int>堪簿。
val box = Box(1)
在學(xué)習(xí)下邊Kotlin 的 泛型特性的時候痊乾,我們先回顧一下 java 中的 泛型 使用
- 通配符上界,只能從中讀取元素椭更,不能添加元素哪审,稱為生產(chǎn)者(Producers),用< ? extends T>表示虑瀑。
- 通配符下界湿滓,只能添加元素,不能直接讀取下界類型的元素舌狗,稱為消費者(Consumers)叽奥,用< ? super T>表示。
3.1 通配符上界
??< ? extends T>(T表示通配符的上界)痛侍,表示可以接收T以及T的子類參數(shù)朝氓,也就是說可以安全的讀取到T的實例,事實上所有的集合元素都是T的子類的實例恋日,但不能向其添加元素膀篮,因為沒法確定添加的實例類型跟定義的類型是否匹配
List<String> strs = new ArrayList<String>();
strs.add("0");
strs.add("1");
List<? extends Object> objs = strs;
objs.get(0); // 可以獲取
objs.add(1); // 但是添加的時候報錯
??經(jīng)過本人測試,不管添加 Int 岂膳,String 類型都提示無法添加誓竿,上面的例子說明了objs可以讀取值,但是再往objs里面添加值的時候谈截,就會出錯筷屡,沒法確定添加的實例類型跟定義的類型是否匹配。
3.2 通配符下界
< ? super T>簸喂,其中T就表示通配符的下界毙死。
舉個栗子:Collection< ? super String>是Collection< String>的父類型,所以可以直接add和set喻鳄,但是get的時候獲取到的類型是Object而不是String類型扼倘。
List<String> strs = new ArrayList<String>();
strs.add("0");
List<? super String> objs = strs;
objs.add("1");
objs.set(0, "2");
// 得到Object類型,如果想要String 還需要強轉(zhuǎn)
Object s = objs.get(0);
Kotlin 中的泛型
??不管是Java還是Kotlin,泛型都是使用擦除來實現(xiàn)的再菊,這意味著當(dāng)你在使用泛型時爪喘,任務(wù)具體的類型信息都被擦除的,你唯一知道的就是你再使用一個對象纠拔。比如秉剑,Box<String>和Box<Int>在運行時是想的類型,都是Box的實例稠诲。在使用泛型時侦鹏,具體類型信息的擦除是我們不不懂得不面對的,在Kotlin中也為我們提供了一些可供參考的解決方案:臀叙、
- 類型協(xié)變
- 類型投射
- 泛型約束
3.3 類型協(xié)變
??假設(shè)我們有一個泛型接口Source< in T, out R >, 其中T由協(xié)變注解in修飾略水,R由協(xié)變注解Out修飾.
internal interface Source<in T, out R> {
// in 函數(shù),可以當(dāng)做參數(shù)使用匹耕,消費聚请,但是不能作為返回值
fun mapT(t: T): Unit
// out 函數(shù),不能用來當(dāng)參數(shù)稳其,不能消費驶赏,但是可以作為返回值
fun nextR(): R
}
in T: ? ? 來確保Source的成員函數(shù)只能消費T類型,而不能返回T類型
out R:? 來確保Source的成員函數(shù)只能返回R類型既鞠,而不能消費R類型
??從上面的解釋中煤傍,我們可以清楚的知道了協(xié)變注解in和out的用意,其實際上是定義了類型參數(shù)在該類或者接口的用途嘱蛋,是用來消費的還是用來返回的蚯姆,對其做了相應(yīng)的限定。
3.4 類型投射
??從上述代碼中我們了解到 泛型的 in 和 out 的使用洒敏,下面我們通過一段 代碼了解 到底什么是 類型投射龄恋。
fun copy(from: Array<out String>, to: Array<Any>) {
// ...
}
fun fill(dest: Array<in String>, value: String) {
// ...
}
??from的泛型參數(shù)使用了協(xié)變注解out修飾,意味著該參數(shù)不能在該函數(shù)中消費凶伙,在該方法中 禁止 對該參數(shù)進行任何操作郭毕。
??對于fill函數(shù)中,dest的泛型參數(shù)使用了協(xié)變注解in修飾函荣,Array<in String>與Java的 Array < ? super String> 相同, 也就是說, 你可以使用CharSequence數(shù)組,或者 Object 數(shù)組作為 fill() 函數(shù)的參數(shù)显押、
??這種聲明在Kotlin中稱為類型投射(type projection),類型投射的主要用于對參數(shù)做了相對因的限定傻挂,避免了對該參數(shù)類的不安全操作乘碑。
3.5 泛型函數(shù)
??類可以有類型參數(shù)。函數(shù)也可以有金拒。類型參數(shù)要放在函數(shù)名稱之前:
fun <T> singletonList(item: T): List<T> {
// ……
}
fun <T> T.basicToString() : String { // 擴展函數(shù)
// ……
}
// 調(diào)用方式 指定 類型為 int
val l = singletonList<Int>(1)