Kotlin類(lèi)型系統(tǒng)其中涉及到一個(gè)很重要的概念就是大家常說(shuō)的可空性以及為什么Kotlin相比Java在一定程度上能降低空指針異常。此外在Kotlin中完全采用和Java不同思路來(lái)定義它的類(lèi)型系統(tǒng)。也正因?yàn)檫@樣類(lèi)型系統(tǒng)天然具有讓Kotlin在空指針異常出現(xiàn)的頻率明顯低于Java出現(xiàn)的頻率的優(yōu)勢(shì)。此外Kotlin考慮使用和Java完全不同類(lèi)型系統(tǒng),以及它是如何去做到極大兼容和互操作。
一、首先思考幾個(gè)概念
在進(jìn)入Kotlin類(lèi)型系統(tǒng)之前,我們不妨先一起來(lái)思考以下幾個(gè)概念,如果不明確這幾個(gè)概念很難從根本上去理解Kotlin類(lèi)型系統(tǒng),以及Kotlin在類(lèi)型系統(tǒng)方面為什么優(yōu)于Java。
- 類(lèi)型的本質(zhì)
類(lèi)型本質(zhì)是什么呢? 為什么變量擁有類(lèi)型? 這兩個(gè)問(wèn)題在維基百科上給出了很好的回答.
類(lèi)型實(shí)際上就是對(duì)數(shù)據(jù)的分類(lèi),決定了該類(lèi)型上可能的值以及該類(lèi)型的值上可以完成的操作。 需要特別去注意一下后面的闡述: "該類(lèi)型上可能的值以及該類(lèi)型的值上可以完成的操作。" 因?yàn)镴ava的類(lèi)型系統(tǒng)其實(shí)并沒(méi)有100%符合這個(gè)規(guī)則,所以這也是Java類(lèi)型系統(tǒng)所存在的問(wèn)題,下面會(huì)做出具體的分析。
- 類(lèi)與類(lèi)型
關(guān)于 類(lèi) 和 類(lèi)型估計(jì)很多開(kāi)發(fā)者往往忽略它們之間的區(qū)別,因?yàn)樵谡嬲膽?yīng)用場(chǎng)景并不會(huì)區(qū)分這么細(xì)。我們?cè)谑褂弥型鶗?huì)把類(lèi)等同于類(lèi)型,實(shí)際上是完全不同兩個(gè)東西。其實(shí)在Java中也有體現(xiàn),例如List<String>、Lis<Integer> 和 List,對(duì)于前者List<String>和List<Integer>只能是類(lèi)型不能說(shuō)是類(lèi), 而對(duì)于List它既可以是List類(lèi)也可以是類(lèi)型(Java中的原生類(lèi)型)。其實(shí)在Kotlin則把這個(gè)概念提升到一個(gè)更高的層次,因?yàn)镵otlin中每個(gè)類(lèi)多了一個(gè)可空類(lèi)型,例如String類(lèi)就對(duì)應(yīng)兩種類(lèi)型String類(lèi)型和String?可空類(lèi)型。而在Java中除了泛型類(lèi)型,每個(gè)類(lèi)只對(duì)應(yīng)一種類(lèi)型(就是類(lèi)的本身),所以往往被忽略。
我們可以把Kotlin中的類(lèi)可分為兩大類(lèi)(Java也可以這樣劃分): 泛型類(lèi)和非泛型類(lèi)
非泛型類(lèi)
先說(shuō)非泛型類(lèi)也就是開(kāi)發(fā)中接觸最多的一般類(lèi),一般的類(lèi)去定義一個(gè)變量的時(shí)候,它的類(lèi)實(shí)際就是這個(gè)變量的類(lèi)型。例如:
var msg: String 這里我們可以說(shuō)String類(lèi)和msg變量的類(lèi)型是一致的。但是在Kotlin中還有一種特殊的類(lèi)型那就是可空類(lèi)型,可以定義為var msg: String?,這里的String類(lèi)和msg變量的String?類(lèi)型就不一樣了。所以在Kotlin中一個(gè)類(lèi)一般至少對(duì)應(yīng)兩種類(lèi)型. 所以類(lèi)和類(lèi)型不是一個(gè)東西。
泛型類(lèi)
泛型類(lèi)比非泛型類(lèi)要更加復(fù)雜,實(shí)際上一個(gè)泛型類(lèi)可以對(duì)應(yīng)無(wú)限種類(lèi)型。為什么這么說(shuō),其實(shí)很容易理解。我們從前面文章知道,在定義泛型類(lèi)的時(shí)候會(huì)定義泛型形參,要想拿到一個(gè)合法的泛型類(lèi)型就需要在外部使用地方傳入具體的類(lèi)型實(shí)參替換定義中的類(lèi)型形參。我們知道在Kotlin中List是一個(gè)類(lèi),它不是一個(gè)類(lèi)型。由它可以衍生成無(wú)限種泛型類(lèi)型例如List<String>、List<Int>、List<List<String>>、List<Map<String,Int>>
- 子類(lèi)、子類(lèi)型與超類(lèi)、超類(lèi)型
我們一般說(shuō)子類(lèi)就是派生類(lèi),該類(lèi)一般會(huì)繼承它的超類(lèi)。例如: class Student: Person(),這里的Student一般稱(chēng)為Person的子類(lèi), Person是Student的超類(lèi)。
而子類(lèi)型和超類(lèi)型定義則完全不一樣,我們從上面類(lèi)和類(lèi)型區(qū)別就知道一個(gè)類(lèi)可以有很多類(lèi)型,那么子類(lèi)型不僅僅是想子類(lèi)那樣繼承關(guān)系那么嚴(yán)格。
子類(lèi)型定義的規(guī)則一般是這樣的: 任何時(shí)候如果需要的是A類(lèi)型值的任何地方,都可以使用B類(lèi)型的值來(lái)替換的,那么就可以說(shuō)B類(lèi)型是A類(lèi)型的子類(lèi)型或者稱(chēng)A類(lèi)型是B類(lèi)型的超類(lèi)型。可以明顯看出子類(lèi)型的規(guī)則會(huì)比子類(lèi)規(guī)則更為寬松。那么我們可以一起分析下面幾個(gè)例子:

注意: 某個(gè)類(lèi)型也是它自己本身的子類(lèi)型,很明顯Person類(lèi)型的值任意出現(xiàn)地方,Person肯定都是可以替換的。屬于子類(lèi)關(guān)系的一般也是子類(lèi)型關(guān)系。像String類(lèi)型值肯定不能替代Int類(lèi)型值出現(xiàn)的地方,所以它們不存在子類(lèi)型關(guān)系
再來(lái)看個(gè)例子,所有類(lèi)的非空類(lèi)型都是該類(lèi)對(duì)應(yīng)的可空類(lèi)型的子類(lèi)型,但是反過(guò)來(lái)說(shuō)就不行,就比如Person非空類(lèi)型是Person?可空類(lèi)型的子類(lèi)型,很明顯嘛,任何Person?可空類(lèi)型出現(xiàn)值的地方,都可以使用Person非空類(lèi)型的值來(lái)替換。其實(shí)這些我在開(kāi)發(fā)過(guò)程中是可以體會(huì)得到的,比如細(xì)心的同學(xué)就會(huì)發(fā)現(xiàn),我們?cè)贙otlin開(kāi)發(fā)過(guò)程,如果一個(gè)函數(shù)接收的是一個(gè)可空類(lèi)型的參數(shù),調(diào)用的地方傳入一個(gè)非空類(lèi)型的實(shí)參進(jìn)去是合法的。但是如果一個(gè)函數(shù)接收的是非空類(lèi)型參數(shù),傳入一個(gè)可空類(lèi)型的實(shí)參編譯器就會(huì)提示你,可能存在空指針問(wèn)題,需要做非空判斷。 因?yàn)槲覀冎婪强疹?lèi)型比可空類(lèi)型更安全。來(lái)幅圖理解下:

二、Java類(lèi)型系統(tǒng)存在空指針異常的本質(zhì)問(wèn)題
有了上述關(guān)于類(lèi)型本質(zhì)的闡述,我們一起來(lái)看下Java中的一些基本類(lèi)型來(lái)套用類(lèi)型本質(zhì)的定義,來(lái)看看有什么問(wèn)題。
使用類(lèi)型的定義驗(yàn)證int類(lèi)型:
例如一個(gè)int類(lèi)型的變量,那么表明它只能存儲(chǔ)int類(lèi)型的數(shù)據(jù),我們都知道它用4個(gè)字節(jié)存儲(chǔ),數(shù)值表示范圍是-2147483648 ~ 2147483647,那么規(guī)定該類(lèi)型可能存在的值,然后我們可以對(duì)該類(lèi)型的值進(jìn)行運(yùn)算操作。似乎沒(méi)毛病,int類(lèi)型和類(lèi)型本質(zhì)闡述契合的是如此完美。但是String類(lèi)型呢?也是這樣的嗎?請(qǐng)接著往下看使用類(lèi)型的定義驗(yàn)證String類(lèi)型或其他定義類(lèi)對(duì)應(yīng)的類(lèi)型:
例如一個(gè)String類(lèi)型的變量,在Java中它卻可以存在兩種值: 一個(gè)是String類(lèi)的實(shí)例另一種則是null。然后我們可以對(duì)這些值進(jìn)行一些操作,第一種String類(lèi)實(shí)例當(dāng)然允許你調(diào)用String類(lèi)所有操作方法,但是對(duì)于第二種null值,操作則非常有限,如果你強(qiáng)行使用null值去操作String類(lèi)中的操作方法,那么恭喜你,你將獲得一個(gè)NullPointerException空指針異常。在Java中為了程序的健壯性,這就要求開(kāi)發(fā)者對(duì)String類(lèi)型的值還得需要做額外的判斷,然后再做相應(yīng)的處理,如果不做額外判斷處理那么就很容易得到空指針異常。 這就出現(xiàn)同一種類(lèi)型變量存在多種值,卻不能得到平等一致的對(duì)待。對(duì)比上述int類(lèi)型的存在的值都是一致對(duì)待,所有該類(lèi)型上所有可能的值都可以進(jìn)行相同的運(yùn)算操作。下面接著看著一個(gè)很有趣例子:

貌似連Java中的instanceof都不承認(rèn)null是一個(gè)String類(lèi)型的值。這兩種值的操作也完全不一樣: 真實(shí)的String允許你調(diào)用它的任何方法,而null值只允許非常有限的操作。那么Kotlin類(lèi)型系統(tǒng)是如何解決這樣的問(wèn)題的呢? 請(qǐng)接著往下看。
三、Kotlin類(lèi)型系統(tǒng)如何解決問(wèn)題(為什么會(huì)設(shè)計(jì)出可空類(lèi)型)
Java中的類(lèi)型系統(tǒng)中String類(lèi)型或其他自定義類(lèi)的類(lèi)型,貌似和類(lèi)型本質(zhì)定義不太符合,該類(lèi)型的所有可能值卻被區(qū)別對(duì)待,存在二義性。還得額外判斷,直接問(wèn)題就是給開(kāi)發(fā)者帶來(lái)了額外負(fù)擔(dān)得做非空判斷,一旦處理不好就會(huì)出現(xiàn)空指針導(dǎo)致程序崩潰。這就是Java中引發(fā)空指針問(wèn)題的本質(zhì)。
抓住問(wèn)題的本質(zhì),Kotlin做一個(gè)很偉大的舉措那就是類(lèi)型的拆分,將Kotlin中所有的類(lèi)型拆分成兩種: 一種是非空類(lèi)型,另一種則是可空類(lèi)型;其中非空類(lèi)型變量不允許null值的賦值操作,換句話(huà)說(shuō)就是String非空類(lèi)型只存在String類(lèi)的實(shí)例不存在null值,所以針對(duì)String非空類(lèi)型的值你可以大膽使用String類(lèi)所有相關(guān)方法,不存在二義性。 當(dāng)然也會(huì)存在null情況,那就可以使用可空類(lèi)型,在使用可空類(lèi)型的變量的時(shí)候編譯器在編譯時(shí)期會(huì)做針對(duì)可空類(lèi)型做一定判斷,如果存在可空類(lèi)型的變量操作該對(duì)應(yīng)類(lèi)的方法,就提示你需要做額外判空處理,這時(shí)候開(kāi)發(fā)者就根據(jù)提示去做判空處理了,想象下都這樣處理了,你的Kotlin代碼還會(huì)出現(xiàn)空指針嗎?(但是有一點(diǎn)很重要就是定義了一個(gè)變量你需要明確它是可空還是非空,如果定義了可空類(lèi)型你就需要對(duì)它負(fù)責(zé),并且編譯器也會(huì)提示幫助你對(duì)它做額外判空處理。)。一起來(lái)看下幾個(gè)例子:
-
1、非空類(lèi)型變量或常量不能接收null值
image -
2、非空類(lèi)型的變量或常量中
is(相當(dāng)于java中instanceof)image -
3、可空類(lèi)型的變量或常量直接操作相應(yīng)方法會(huì)有明顯的編譯錯(cuò)誤并提示判空操作
image
然而上面那些都是Java給不了你的,所以Java程序中一般會(huì)存在三種狀態(tài): 一種佛系判空,經(jīng)常會(huì)出現(xiàn)空指針問(wèn)題。另一種就是一股腦全部判空,可是代碼中充斥著if-else代碼,可讀性非常差。最后一種就是非常熟悉程序邏輯以及數(shù)據(jù)流向的開(kāi)發(fā)者可以正常判斷出哪里需要判空處理,哪里可以不需要,這一種對(duì)開(kāi)發(fā)者要求極高,因?yàn)槿丝偸菚?huì)犯錯(cuò)的。
四、可空類(lèi)型
- 1、安全調(diào)用運(yùn)算符 "?."
?.相當(dāng)于判空處理,如果不為null就執(zhí)行?.后面的表達(dá)式,否則就返回null
text?.substring(0,2) //相當(dāng)于 if(text != null) text.substring(0,2) else null
其實(shí)Kotlin為了類(lèi)型判空處理可算是操碎了心,我們都知道在Java中做判空處理無(wú)非就是if-else或? xxx : xxx三目運(yùn)算符來(lái)實(shí)現(xiàn)。但是有時(shí)候出現(xiàn)嵌套判空的時(shí)候整個(gè)代碼就是一個(gè)“箭頭”,可讀性就很差了。由以上例子可知?.比if-else省了很多代碼,這還無(wú)法完全顯露它的優(yōu)點(diǎn),下面這個(gè)例子就更加明顯了。
Java中的if-else 嵌套處理

Kotlin中的安全調(diào)用運(yùn)算符?.鏈?zhǔn)秸{(diào)用處理
對(duì)比兩種方式的實(shí)現(xiàn)你會(huì)不會(huì)覺(jué)得Kotlin也許更適合你呢,利用?.鏈?zhǔn)秸{(diào)用的方式把嵌套if-else處理解開(kāi)了。
- 2、Elvis運(yùn)算符 "?:"
如果?:前面表達(dá)式為null, 就執(zhí)行?:后面的表達(dá)式,它一般會(huì)和?.一起使用。(注意: 它與Java中的? xxx : xxx 三目運(yùn)算符不一樣)
carbon (29).png
- 3、安全類(lèi)型轉(zhuǎn)化運(yùn)算符 as?
如果類(lèi)型轉(zhuǎn)化失敗就返回null值,否則返回正確的類(lèi)型轉(zhuǎn)化后的值
val student = person as? Student//相當(dāng)于 if(person is Student) person as Student else null
- 4、非空斷言運(yùn)算符 !! 與 契約(contract) 簡(jiǎn)化非空表達(dá)式
非空斷言運(yùn)算符!!, 是強(qiáng)制告訴編譯器這個(gè)變量的值不可能null,存在使用風(fēng)險(xiǎn)。一旦存在為null直接拋出空指針異常。
很多Kotlin開(kāi)發(fā)者很厭惡這個(gè)操作符,覺(jué)得寫(xiě)起來(lái)不優(yōu)雅很影響代碼的可讀性,
其實(shí)是非空斷言的使用場(chǎng)景是存在的,例如你已經(jīng)在一個(gè)函數(shù)中對(duì)某個(gè)變量進(jìn)行判空處理了,但是后面邏輯中再次使用到了它并且你可以確定它不可能為空,可能此時(shí)編譯器無(wú)法識(shí)別它是否是非空,但由于它又是一個(gè)可空類(lèi)型,那么它又會(huì)提示你進(jìn)行判空處理,很煩人是不,很多人這時(shí)候可能就采用了 !! 確實(shí)缺乏可讀性。
針對(duì)上述問(wèn)題,除了之前文章中給出解決方案,這次又提供一個(gè)新的解決方案,那就是契約(實(shí)際上主動(dòng)告訴編譯器某個(gè)規(guī)則,這樣它就不會(huì)提示做判空處理了) 契約官方正式提出來(lái)是Kotlin1.3的版本,雖然還處于Experimental(比如自定義契約)中,但是實(shí)際上Kotlin內(nèi)部代碼,早就使用了契約

一起來(lái)瞅瞅內(nèi)置契約的內(nèi)部實(shí)現(xiàn)源碼
5、內(nèi)置標(biāo)準(zhǔn)庫(kù)函數(shù) let、also、run、apply、with 簡(jiǎn)化可空表達(dá)式
關(guān)于標(biāo)準(zhǔn)庫(kù)函數(shù) let、also、run、apply、with,可參考我之前的一篇文章 [譯]掌握Kotlin中的標(biāo)準(zhǔn)庫(kù)函數(shù): run、with、let、also和apply6、兼容Java的平臺(tái)類(lèi)型
通過(guò)上述我們可以知道在Kotlin中擁有著與Java中完全不一樣的類(lèi)型系統(tǒng)。在Java中是不存在所謂的可空類(lèi)型和非空類(lèi)型。但是我們都知道Kotlin與Java的互操性很強(qiáng),幾乎是完全兼容Java。那么Kotlin是如何兼容Java中的變量類(lèi)型的呢?我們?cè)贙otlin中肯定需要經(jīng)常調(diào)用Java代碼,有的人可能會(huì)回答說(shuō)Java中使用@NotNull和@Nullable注解來(lái)標(biāo)識(shí)。確實(shí)Kotlin可以識(shí)別多種不同風(fēng)格的注解,包括javax.annotation、android.support.annotation、org.jetbrains.annotation等。但是一些之前的第三方庫(kù)并沒(méi)有寫(xiě)的這么規(guī)范,顯然無(wú)法通過(guò)這種方式完全解決這個(gè)問(wèn)題。
所以Kotlin引入一種新的概念叫做: 平臺(tái)類(lèi)型,平臺(tái)類(lèi)型本質(zhì)上就是Kotlin不知道可空性信息的類(lèi)型,既可以把它當(dāng)做可空類(lèi)型又可以把它當(dāng)做非空類(lèi)型。 這就意味你要像Java代碼中一樣對(duì)你在這個(gè)類(lèi)型上做的操作負(fù)全部責(zé)任,說(shuō)的有味道點(diǎn)就是你在Java中拉的便便,Kotlin是不會(huì)給你擦屁股的。所以對(duì)于Java中函數(shù)參數(shù),Kotlin去調(diào)用的時(shí)候系統(tǒng)默認(rèn)會(huì)處理可空類(lèi)型(為了安全性考慮),如果你明確了不為空,可以直接把它修改為非空類(lèi)型,系統(tǒng)也是不為報(bào)編譯錯(cuò)誤的,但是一旦這樣處理了,你必須保證不能為空。
那么問(wèn)題來(lái)了,很多人就疑問(wèn)出于安全性考慮為什么不直接全部轉(zhuǎn)化可空類(lèi)型呢? 實(shí)際上這種方案看似可行,實(shí)際上有點(diǎn)不妥,對(duì)于一些明確不可能為空的變量還需要做大量額外的判空操作就顯得冗余。否則非空類(lèi)型就沒(méi)有存在的意義了。
五、基本數(shù)據(jù)類(lèi)型和其他基本類(lèi)型
- 1、基本數(shù)據(jù)類(lèi)型
我們都知道在Java中針對(duì)基本數(shù)據(jù)類(lèi)型和包裝類(lèi)型做了區(qū)分。例如一個(gè)基本數(shù)據(jù)類(lèi)型int的變量直接存儲(chǔ)了它的值。而一個(gè)引用類(lèi)型(包裝類(lèi)型) String的變量?jī)H僅存儲(chǔ)的是指向該對(duì)象的內(nèi)存地址的引用。基本數(shù)據(jù)類(lèi)型有著天然的高效存儲(chǔ)以及傳遞的優(yōu)勢(shì),但是不能直接調(diào)用這些類(lèi)型的方法,而且在Java中集合中不能將它作為泛型實(shí)參類(lèi)型。
實(shí)際上在Kotlin中并沒(méi)有像Java那樣分為了基本數(shù)據(jù)類(lèi)型和包裝類(lèi)型,在Kotlin中永遠(yuǎn)是同一種類(lèi)型。很多人估計(jì)會(huì)問(wèn)了既然在Kotlin中基本數(shù)據(jù)類(lèi)型和包裝類(lèi)型是一樣的,那么是不是意味著Kotlin是使用引用類(lèi)型來(lái)保存數(shù)據(jù)呢?是不是非常低效呢?不是這樣的,Kotlin在運(yùn)行時(shí)盡量會(huì)把Int等類(lèi)型轉(zhuǎn)換成Java中的int基本數(shù)據(jù)類(lèi)型,而遇到類(lèi)似集合或泛型的時(shí)候就會(huì)轉(zhuǎn)化成Java中對(duì)應(yīng)的Integer等包裝類(lèi)型。這實(shí)際上是一個(gè)底層優(yōu)化,至于什么場(chǎng)景轉(zhuǎn)化成int,什么場(chǎng)景轉(zhuǎn)化成Integer,關(guān)于這塊可以參考之前一篇有關(guān)內(nèi)聯(lián)類(lèi)自動(dòng)裝箱和拆箱的文章:[譯]Kotlin中內(nèi)聯(lián)類(lèi)的自動(dòng)裝箱和高性能探索(二)
基本數(shù)據(jù)類(lèi)型也分為可空類(lèi)型和非空類(lèi)型, 具體可參考如下的類(lèi)型層次結(jié)構(gòu)圖:
- 2、Any和Any?類(lèi)型
Any類(lèi)型是所有非空類(lèi)型的超類(lèi)型,Any?類(lèi)型則是所有的類(lèi)型的超類(lèi)型,即是非空類(lèi)型的超類(lèi)型也是所有可空類(lèi)型的超類(lèi)型。因?yàn)锳ny?是Any的超類(lèi)型。具體的層次可參考下面這張圖:
- 3、Unit類(lèi)型
Unit類(lèi)型也即是Kotlin中的空類(lèi)型,相當(dāng)于Java中的void類(lèi)型,默認(rèn)情況下它可以被省略
- 4、Nothing類(lèi)型
Nothing類(lèi)型是所有類(lèi)型的子類(lèi)型,它既是所有非空類(lèi)型的子類(lèi)型也是所有可空類(lèi)型的子類(lèi)型,因?yàn)镹othing是Nothing?的子類(lèi)型,然而Nothing?又是所有可空類(lèi)型的子類(lèi)型。 具體可以看下如下的層次結(jié)構(gòu)圖:
六、集合和數(shù)組類(lèi)型
- 1、可變集合與只讀集合之間的區(qū)別和聯(lián)系(以Collection集合為例)
Collection只讀集合與MutableCollectio可變集合區(qū)別:
在Collection只具有訪(fǎng)問(wèn)元素的方法,不具有類(lèi)似add、remove、clear之類(lèi)的方法,而在MutableCollection中則相比Collection多出了修改元素的方法。
Collection只讀集合與MutableCollectio可變集合聯(lián)系:
MutableCollection實(shí)際上是Collection集合接口的子接口,他們之間是繼承關(guān)系。
- 2、集合之間類(lèi)的關(guān)系
通過(guò)Collection.kt文件中可以了解到有這些集合Iterable(只讀迭代器)和MutableIterable(可變迭代器)、Collection和MutableCollection、List和MutableList、Set和MutableSet、Map和MutableMap。那么它們之間的類(lèi)關(guān)系圖是怎樣的。
Iterable和MutableIterable接口分別是只讀和可變集合的父接口,Collection繼承Iterable然后List、Set接口繼承自Collection,Map接口比較特殊它是單獨(dú)的接口,然后MutableMap接口是繼承自Map.
- 3、Java中的集合與Kotlin中集合對(duì)應(yīng)關(guān)系
我們剛剛說(shuō)到在Kotlin中集合的設(shè)計(jì)與Java不一樣,但是每一個(gè)Kotlin的接口都是其對(duì)應(yīng)的Java集合接口的一個(gè)實(shí)例,也就是在Kotlin中集合與Kotlin中的集合存在一定的對(duì)應(yīng)關(guān)系。Java中的ArrayList類(lèi)和HashSet類(lèi)實(shí)際上Kotlin中的MutableList和MutableSet集合接口的實(shí)現(xiàn)類(lèi)。把這種關(guān)系加上,上面的類(lèi)關(guān)系圖可以進(jìn)一步完善。
- 4、集合的初始化
由于在Kotlin中集合主要分為了只讀集合和可變集合,那么初始化只讀集合和可變集合的函數(shù)也不一樣。以L(fǎng)ist集合為例,對(duì)于只讀集合初始化一般采用listOf()方法,對(duì)于可變集合初始化一般采用mutableListOf()或者直接創(chuàng)建ArrayList<E>,因?yàn)閙utableListOf()內(nèi)部實(shí)現(xiàn)也是也還是采用創(chuàng)建ArrayList,這個(gè)ArrayList實(shí)際上是Java中的java.util.ArrayList<E>,只不過(guò)在Kotlin中使用typealias(關(guān)于typealias的使用之前博客有過(guò)詳細(xì)介紹)取了別名而已。關(guān)于具體內(nèi)容請(qǐng)參考這個(gè)類(lèi)kotlin.collections.TypeAliasesKt實(shí)現(xiàn)
- 5、集合使用的注意事項(xiàng)
注意點(diǎn)一: 在代碼的任何地方都優(yōu)先使用只讀集合,只在需要修改集合的情況下才去使用可變集合
注意點(diǎn)二: 只讀集合不一定是不可變的,關(guān)于這個(gè)只讀和不可變類(lèi)似于val的只讀和不可變?cè)怼?/strong>
注意點(diǎn)三: 不能把一個(gè)只讀類(lèi)型的集合作為參數(shù)傳遞給一個(gè)帶可變類(lèi)型集合的函數(shù)。
- 6、平臺(tái)類(lèi)型的集合轉(zhuǎn)化規(guī)則
正如前面所提及的可空性平臺(tái)類(lèi)型一樣,Kotlin中無(wú)法知道可空性信息的類(lèi)型,既可以把它當(dāng)做可空類(lèi)型又可以把它當(dāng)做非空類(lèi)型。集合的平臺(tái)類(lèi)型和這個(gè)類(lèi)似,在Java中聲明的集合類(lèi)型的變量也被看做平臺(tái)類(lèi)型。一個(gè)平臺(tái)類(lèi)型的集合本質(zhì)上就是可變性未知的集合,Kotlin中可以把它看做是只讀的集合或者是可變的集合. 實(shí)際上這都不是很重要,因?yàn)槟阒恍枰鶕?jù)你的需求選擇即可,想要執(zhí)行的所有操作都能正常工作,它不像可空性平臺(tái)存在額外判斷操作以及空指針風(fēng)險(xiǎn)。
注意: 可是當(dāng)你決定使用哪一種Kotlin類(lèi)型表示Java中集合類(lèi)型的變量時(shí),需要考慮以下三種情況:
- 1、集合是否為空?
如果為空轉(zhuǎn)換成Kotlin中集合后面添加 ?,例如Java中的
List<String>轉(zhuǎn)化成Kotlin中的List<String>?
- 2、集合中的元素是否為空?
如果為空轉(zhuǎn)換成Kotlin中集合泛型實(shí)參后面添加 ?,例如Java中的
List<String>轉(zhuǎn)化成Kotlin中的List<String?>
- 3、操作方法會(huì)不會(huì)修改集合?(集合的只讀或可變)
如果是只讀的,例如Java中的
List<String>轉(zhuǎn)化成Kotlin中的List<String>;如果是可變的,例如Java中的List<String>轉(zhuǎn)化成Kotlin中的MutableList<String>.
注意: 當(dāng)然上面三種情況可以一種或多種同時(shí)出現(xiàn),那么轉(zhuǎn)化成Kotlin中的集合類(lèi)型也是多種情況最終重組的類(lèi)型。
七、總結(jié)
到這里有關(guān)Kotlin的類(lèi)型系統(tǒng)基本就說(shuō)得差不多,該涉及到的內(nèi)容基本都涉及了。其實(shí)仔細(xì)去體會(huì)下為什么Kotlin的類(lèi)型系統(tǒng)要如此設(shè)計(jì),確實(shí)是它一定道理的。我們經(jīng)常聽(tīng)別人夸Kotlin比Java優(yōu)點(diǎn)是啥,很多人都說(shuō)少了很多空指針異常,但是為什么能Kotlin相比Java有更少的空指針異常相信這篇文章也足夠回答你了吧。