系列文章全部為本人的學(xué)習(xí)筆記,若有任何不妥之處,隨時(shí)歡迎拍磚指正。如果你覺(jué)得我的文章對(duì)你有用,歡迎關(guān)注我,我們一起學(xué)習(xí)進(jìn)步!
Kotlin學(xué)習(xí)筆記(1)- 環(huán)境配置
Kotlin學(xué)習(xí)筆記(2)- 空安全
Kotlin學(xué)習(xí)筆記(3)- 語(yǔ)法
Kotlin學(xué)習(xí)筆記(4)- 流程控制
Kotlin學(xué)習(xí)筆記(5)- 類(lèi)
Kotlin學(xué)習(xí)筆記(6)- 屬性
Kotlin學(xué)習(xí)筆記(7)- 接口
Kotlin學(xué)習(xí)筆記(8)- 擴(kuò)展
Kotlin學(xué)習(xí)筆記(8)- 擴(kuò)展(續(xù))
Kotlin學(xué)習(xí)筆記(9)- 數(shù)據(jù)類(lèi)
Kotlin學(xué)習(xí)筆記(10)- 泛型
Kotlin學(xué)習(xí)筆記(11)- 內(nèi)部類(lèi)和嵌套類(lèi)
Kotlin學(xué)習(xí)筆記(12)- 委托
Kotlin學(xué)習(xí)筆記(13)- 函數(shù)式編程
Kotlin學(xué)習(xí)筆記(14)- lambda
一、java泛型
泛型的本質(zhì)是參數(shù)化類(lèi)型,也就是說(shuō)所操作的數(shù)據(jù)類(lèi)型被指定為一個(gè)參數(shù)。這種參數(shù)類(lèi)型可以用在類(lèi)、接口和方法的創(chuàng)建中,分別稱(chēng)為泛型類(lèi)、泛型接口、泛型方法。 Java語(yǔ)言引入泛型的好處是安全簡(jiǎn)單。
泛型的好處是在編譯的時(shí)候檢查類(lèi)型安全,并且所有的強(qiáng)制轉(zhuǎn)換都是自動(dòng)和隱式的,以提高代碼的重用率。
——引自“百度百科,java泛型”
java泛型大家應(yīng)該都有所了解,個(gè)人理解的話(huà),泛型就是用同樣的邏輯處理不同類(lèi)型的數(shù)據(jù)。在思想上和方法類(lèi)似,方法是用相同的邏輯處理不同值的數(shù)據(jù),而泛型是用相同的邏輯處理不同類(lèi)型的數(shù)據(jù),都是封裝與抽象思想的應(yīng)用。下面簡(jiǎn)單總結(jié)一下java中泛型的知識(shí)點(diǎn)。
1.用在類(lèi)上
class Data<T>{}
2.用在接口上
interface Data<T>{}
3.用在方法上
public <T> void data(T t){}
4.類(lèi)型通配符
用?表示任何類(lèi)型,結(jié)合extends和super關(guān)鍵字,可以限定類(lèi)型的上限和下限。
class Data<T>{}
// extends關(guān)鍵字限定類(lèi)型上限,表示類(lèi)型必須是String或String的子類(lèi)
public void dataUpper(Data<? extends String> d){}
// super關(guān)鍵字限定類(lèi)型下限,表示類(lèi)型必須是String或String的父類(lèi)
public void dataLower(Data<? super String> d){}
《 EffectiveJava》中解析,使用通配符為了提高API的使用靈活性(Use bounded wildcards to increase APIflexibility)。
5.泛型類(lèi)型不可變
例如String是Object的子類(lèi),但是List<String>卻不是List<Object>的子類(lèi)
String a = "";
Object b = a; // OK, String是Object的子類(lèi)
List<String> c = new ArrayList<>();
List<Object> d = c; // Error,編譯錯(cuò)誤,泛型類(lèi)型不可變,所以List<String>和List<Object>是兩個(gè)類(lèi)型
二、Kotlin泛型
kotlin中的泛型,和java中大體是相同的,也有很多相似的特性。這其實(shí)很好理解,因?yàn)檫@些特性確實(shí)是面向?qū)ο笏枷氲暮芎皿w現(xiàn)。
1.泛型類(lèi)
class Data<T>(var t : T)
2.泛型接口
interface Data<T>
3.泛型函數(shù)
fun <T> logic(t : T){}
4.類(lèi)型擦除
在上面的java泛型特性中沒(méi)有講到這一點(diǎn),其實(shí)是相同的,我們先來(lái)看一段代碼
class Data<T>{}
Log.d("test", Data<Int>().javaClass.name)
Log.d("test", Data<String>().javaClass.name)
// 輸出
com.study.jcking.weatherkotlin.exec.Data
com.study.jcking.weatherkotlin.exec.Data
聲明了一個(gè)泛型類(lèi)Data<T>,并實(shí)現(xiàn)了兩種不同類(lèi)型的實(shí)例。但是在獲取類(lèi)名是,卻發(fā)現(xiàn)得到了同樣的結(jié)果com.study.jcking.weatherkotlin.exec.Data,這其實(shí)是在編譯期擦除了泛型類(lèi)型聲明。那么我們考慮這樣一個(gè)問(wèn)題:
var a : ArrayList<Int>
var b : ArrayList<Any> = a
b.add("c")
如果泛型是類(lèi)型擦除的,那么上面的代碼應(yīng)該是可以執(zhí)行的。但是我們都知道這其實(shí)是不應(yīng)該被允許的。而事實(shí)上,這確實(shí)是不被允許的,這就要說(shuō)到之前提到的java泛型的不可變性了,在kotlin中也是一樣的。所以上面的代碼,其實(shí)在第二行編輯器就已經(jīng)報(bào)錯(cuò)了。
5.類(lèi)型協(xié)變
那么既然泛型類(lèi)型是不可變的,那么我們?cè)倏紤]一個(gè)問(wèn)題
open class A
class B : A()
fun copy(from: Array<A>, to: Array<A>) {
for (i in from.indices)
to[i] = from[i]
}
var arrayA : Array<A> = arrayOf(A(), A())
var arrayB : Array<B> = arrayOf(B(), B())
copy(arrayB, arrayA)
我們定義了open類(lèi)A和類(lèi)B,并讓B繼承A,定義了一個(gè)copy函數(shù)。在下面調(diào)用copy方法時(shí),我們傳入的第一個(gè)參數(shù)是Array<B>類(lèi)型的,按照泛型類(lèi)型的不可變性,這里編輯器確實(shí)給出了錯(cuò)誤提示。但是我們知道B是A的子類(lèi),我們不希望將arrayB放到一個(gè)Array<A>中再進(jìn)行調(diào)用,這無(wú)疑是很煩瑣很不聰明的。
在java中,我們可以用通配符解決這個(gè)問(wèn)題
public void copy(Array<? extends A> from, Array<A> to){}
在kotlin中,同樣為我們準(zhǔn)備了解決方案,那就是協(xié)變注解修飾符:in以及out。我們來(lái)看一個(gè)接口
internal interface Source<in T, out R> {
fun mapT(t: T): Unit
fun nextR(): R
}
in T:來(lái)確保Source的成員函數(shù)只能消費(fèi)T類(lèi)型,而不能返回T類(lèi)型,我們也稱(chēng)in修飾的參數(shù)為“消費(fèi)者”
out R:來(lái)確保Source的成員函數(shù)只能返回R類(lèi)型,而不能消費(fèi)R類(lèi)型,我們也稱(chēng)out修飾的參數(shù)為“生產(chǎn)者”
那么我們?cè)賮?lái)看之前的copy問(wèn)題,fun copy(from: Array<A>, to: Array<A>),可以看出,from是生產(chǎn)者,to是消費(fèi)者。我們可以改成這樣
fun copy(from: Array<out A>, to: Array<in A>) {
for (i in from.indices)
to[i] = from[i]
}
var arrayA : Array<A> = arrayOf(A(), A())
var arrayB : Array<B> = arrayOf(B(), B())
copy(arrayB, arrayA)
編譯通過(guò)!
關(guān)于
out和in這兩個(gè)關(guān)鍵字,我自己是這么記憶的。out是用來(lái)輸出的,所以只能作為返回類(lèi)型;in是用來(lái)輸入的,所以只能作為消費(fèi)類(lèi)型。而從上面的copy方法中可以看出,out類(lèi)似于java中的extends,用來(lái)界定類(lèi)型上限,in類(lèi)似于java中的super,用來(lái)界定類(lèi)型下限
6.星號(hào)投射
有些時(shí)候, 你可能想表示你并不知道類(lèi)型參數(shù)的任何信息, 但是仍然希望能夠安全地使用它. 這里所謂”安全地使用”是指, 對(duì)泛型類(lèi)型定義一個(gè)類(lèi)型投射, 要求這個(gè)泛型類(lèi)型的所有的實(shí)體實(shí)例, 都是這個(gè)投射的子類(lèi)型.
對(duì)于這個(gè)問(wèn)題, Kotlin 提供了一種語(yǔ)法, 稱(chēng)為 星號(hào)投射(star-projection):
- 假如類(lèi)型定義為
Foo<out T>, 其中 T 是一個(gè)協(xié)變的類(lèi)型參數(shù), 上界(upper bound)為 TUpper ,Foo<*>等價(jià)于Foo<out TUpper>. 它表示, 當(dāng) T 未知時(shí), 你可以安全地從Foo<*>中 讀取TUpper 類(lèi)型的值. - 假如類(lèi)型定義為
Foo<in T>, 其中 T 是一個(gè)反向協(xié)變的類(lèi)型參數(shù),Foo<*>等價(jià)于Foo<in Nothing>. 它表示, 當(dāng) T 未知時(shí), 你不能安全地向Foo<*>寫(xiě)入 任何東西. - 假如類(lèi)型定義為
Foo<T>, 其中 T 是一個(gè)協(xié)變的類(lèi)型參數(shù), 上界(upper bound)為 TUpper , 對(duì)于讀取值的場(chǎng)合,Foo<*>等價(jià)于Foo<out TUpper>, 對(duì)于寫(xiě)入值的場(chǎng)合, 等價(jià)于Foo<in Nothing>.
如果一個(gè)泛型類(lèi)型中存在多個(gè)類(lèi)型參數(shù), 那么每個(gè)類(lèi)型參數(shù)都可以單獨(dú)的投射. 比如, 如果類(lèi)型定義為interface Function<in T, out U> , 那么可以出現(xiàn)以下幾種星號(hào)投射:
-
Function<*, String>, 代表Function<in Nothing, String>; -
Function<Int, *>, 代表Function<Int, out Any?>; -
Function<*, *>, 代表Function<in Nothing, out Any?>.
注意: 星號(hào)投射與 Java 的原生類(lèi)型(raw type)非常類(lèi)似, 但可以安全使用