Kotlin類型系統(tǒng)
這篇文章一起來(lái)看下 Kotlin 中類型系統(tǒng),其中涉及到一個(gè)很重要的概念就是大家常說(shuō)的可空性以及為什么 Kotlin 相比 Java 在一定程度上能降低空指針異常。此外在 Kotlin 中完全采用和 Java 不同思路來(lái)定義它的類型系統(tǒng)。也正因?yàn)檫@樣類型系統(tǒng)天然具有讓 Kotlin 在空指針異常出現(xiàn)的頻率明顯低于 Java出現(xiàn)的頻率的優(yōu)勢(shì)。此外 Kotlin 考慮使用和 Java 完全不同類型系統(tǒng),以及它是如何去做到極大兼容和互操作。
1. 梳理概念
在學(xué)習(xí) Kotlin 類型系統(tǒng)之前,我們不妨先一起來(lái)思考以下幾個(gè)概念,如果不明確這幾個(gè)概念很難從根本上去理解 Kotlin 類型系統(tǒng),以及 Kotlin 在類型系統(tǒng)方面為什么優(yōu)于 Java。
1.1 類型的本質(zhì)
類型本質(zhì)是什么呢? 為什么變量擁有類型? 這兩個(gè)問題在維基百科上給出了很好的回答:
類型實(shí)際上就是對(duì)數(shù)據(jù)的分類,決定了該類型上可能的值以及該類型的值上可以完成的操作。 需要特別去注意一下后面的闡述: “該類型上可能的值以及該類型的值上可以完成的操作。” 因?yàn)镴ava 的類型系統(tǒng)其實(shí)并沒有 100% 符合這個(gè)規(guī)則,所以這也是 Java 類型系統(tǒng)所存在的問題,下面會(huì)做出具體的分析。
1.2 類與類型
關(guān)于 類 和 **類型 **估計(jì)很多開發(fā)者往往忽略它們之間的區(qū)別,因?yàn)樵谡嬲膽?yīng)用場(chǎng)景并不會(huì)區(qū)分這么細(xì)。我們?cè)谑褂弥型鶗?huì)把類等同于類型,實(shí)際上是完全不同兩個(gè)東西。其實(shí)在 Java 中也有體現(xiàn),例如 List<String>、Lis<Integer>
和 List
,對(duì)于前者 List<String>
和 List<Integer>
只能是類型不能說(shuō)是類,而對(duì)于 List
它既可以是 List 類也可以是類型(Java 中的原生類型)。
其實(shí)在 Kotlin 則把這個(gè)概念提升到一個(gè)更高的層次,因?yàn)?Kotlin 中每個(gè)類多了一個(gè)可空類型,例如String
類就對(duì)應(yīng)兩種類型 String
類型和 String?
可空類型。而在 Java 中除了泛型類型,每個(gè)類只對(duì)應(yīng)一種類型(就是類的本身),所以往往被忽略。
我們可以把 Kotlin 中的類可分為兩大類(Java 也可以這樣劃分): 泛型類和非泛型類。
非泛型類
先說(shuō)非泛型類也就是開發(fā)中接觸最多的一般類,一般的類去定義一個(gè)變量的時(shí)候,它的類實(shí)際就是這個(gè)變量的類型。例如:
var msg: String
這里我們可以說(shuō)String
類 和 msg
變量的類型是一致的。但是在 Kotlin 中還有一種特殊的類型那就是可空類型,可以定義為var msg: String?
,這里的String
類和msg
變量的String?
類型就不一樣了。所以在 Kotlin 中一個(gè)類一般至少對(duì)應(yīng)兩種類型. 所以類和類型不是一個(gè)東西。
泛型類
泛型類比非泛型類要更加復(fù)雜,實(shí)際上一個(gè)泛型類可以對(duì)應(yīng)無(wú)限種類型。為什么這么說(shuō),其實(shí)很容易理解。我們從前面文章知道,在定義泛型類的時(shí)候會(huì)定義泛型形參,要想拿到一個(gè)合法的泛型類型就需要在外部使用地方傳入具體的類型實(shí)參替換定義中的類型形參。我們知道在 Kotlin 中 List
是一個(gè)類,它不是一個(gè)類型。由它可以衍生成無(wú)限種泛型類型例如List<String>、List<Int>、List<List<String>>、List<Map<String,Int>>
。
1.3 子類、子類型與超類、超類型
我們一般說(shuō)子類就是派生類,該類一般會(huì)繼承它的超類。例如: class Student: Person()
,這里的Student
一般稱為Person
的子類, Person
是Student
的超類。
而子類型和超類型定義則完全不一樣,我們從上面類和類型區(qū)別就知道一個(gè)類可以有很多類型,那么子類型不僅僅是想子類那樣繼承關(guān)系那么嚴(yán)格。
子類型定義的規(guī)則一般是這樣的: 任何時(shí)候如果需要的是A類型值的任何地方,都可以使用B類型的值來(lái)替換的,那么就可以說(shuō)B類型是A類型的子類型或者稱A類型是B類型的超類型??梢悦黠@看出子類型的規(guī)則會(huì)比子類規(guī)則更為寬松。那么我們可以一起分析下面幾個(gè)例子:
Tips:某個(gè)類型也是它自己本身的子類型,很明顯 Person 類型的值任意出現(xiàn)地方,Person 肯定都是可以替換的。屬于子類關(guān)系的一般也是子類型關(guān)系。像String類型值肯定不能替代Int類型值出現(xiàn)的地方,所以它們不存在子類型關(guān)系
再來(lái)看個(gè)例子,所有類的非空類型都是該類對(duì)應(yīng)的可空類型的子類型,但是反過來(lái)說(shuō)就不行,就比如Person
非空類型是Person?
可空類型的子類型,很明顯嘛,任何Person?
可空類型出現(xiàn)值的地方,都可以使用Person
非空類型的值來(lái)替換。
其實(shí)這些我在開發(fā)過程中是可以體會(huì)得到的,比如細(xì)心的同學(xué)就會(huì)發(fā)現(xiàn),我們?cè)?Kotlin 開發(fā)過程,如果一個(gè)函數(shù)接收的是一個(gè)可空類型的參數(shù),調(diào)用的地方傳入一個(gè)非空類型的實(shí)參進(jìn)去是合法的。但是如果一個(gè)函數(shù)接收的是非空類型參數(shù),傳入一個(gè)可空類型的實(shí)參編譯器就會(huì)提示你,可能存在空指針問題,需要做非空判斷。 因?yàn)槲覀冎婪强疹愋捅瓤煽疹愋透踩?。?lái)幅圖理解下:
2. Java類型系統(tǒng)存在NPE的本質(zhì)原因
有了上述關(guān)于類型本質(zhì)的闡述,我們一起來(lái)看下 Java 中的一些基本類型來(lái)套用類型本質(zhì)的定義,來(lái)看看有什么問題。
使用類型的定義驗(yàn)證int
類型
例如一個(gè) int
類型的變量,那么表明它只能存儲(chǔ) int
類型的數(shù)據(jù),我們都知道它用4個(gè)字節(jié)存儲(chǔ),數(shù)值表示范圍是-2147483648 ~ 2147483647,那么規(guī)定該類型可能存在的值,然后我們可以對(duì)該類型的值進(jìn)行運(yùn)算操作。似乎沒毛病,int
類型和類型本質(zhì)闡述契合的是如此完美。
但是String
類型呢?也是這樣的嗎?請(qǐng)接著往下看:
使用類型的定義驗(yàn)證String
類型或其他定義類對(duì)應(yīng)的類型
例如一個(gè) String
類型的變量,在 Java 中它卻可以存在兩種值:一個(gè)是String
類的實(shí)例另一種則是null
。然后我們可以對(duì)這些值進(jìn)行一些操作,第一種String
類實(shí)例當(dāng)然允許你調(diào)用String
類所有操作方法,但是對(duì)于第二種null
值,操作則非常有限,如果你強(qiáng)行使用null
值去操作String
類中的操作方法,那么恭喜你,你將獲得一個(gè)NullPointerException
空指針異常。
在 Java 中為了程序的健壯性,這就要求開發(fā)者對(duì) String
類型的值還得需要做額外的判斷,然后再做相應(yīng)的處理,如果不做額外判斷處理那么就很容易得到空指針異常。
這就出現(xiàn)同一種類型變量存在多種值,卻不能得到平等一致的對(duì)待。對(duì)比上述 int
類型的存在的值都是一致對(duì)待,所有該類型上所有可能的值都可以進(jìn)行相同的運(yùn)算操作。下面接著看著一個(gè)很有趣例子:
貌似連 Java 中的instanceof
都不承認(rèn)null
是一個(gè)String
類型的值。這兩種值的操作也完全不一樣:真實(shí)的String
允許你調(diào)用它的任何方法,而null
值只允許非常有限的操作。那么 Kotlin 類型系統(tǒng)是如何解決這樣的問題的呢? 請(qǐng)接著往下看。
3. Kotlin類型系統(tǒng)如何解決問題
Java 中的類型系統(tǒng)中String
類型或其他自定義類的類型,貌似和類型本質(zhì)定義不太符合,該類型的所有可能值卻被區(qū)別對(duì)待,存在二義性。還得額外判斷,直接問題就是給開發(fā)者帶來(lái)了額外負(fù)擔(dān)得做非空判斷,一旦處理不好就會(huì)出現(xiàn)空指針導(dǎo)致程序崩潰。這就是Java中引發(fā)空指針問題的本質(zhì)。
抓住問題的本質(zhì),Kotlin 做一個(gè)很偉大的舉措那就是類型的拆分,將 Kotlin 中所有的類型拆分成兩種:一種是非空類型,另一種則是可空類型;其中非空類型變量不允許null
值的賦值操作,換句話說(shuō)就是String
非空類型只存在String
類的實(shí)例不存在null
值,所以針對(duì)String
非空類型的值你可以大膽使用String
類所有相關(guān)方法,不存在二義性。
當(dāng)然也會(huì)存在 null 情況,那就可以使用可空類型,在使用可空類型的變量的時(shí)候編譯器在編譯時(shí)期會(huì)做針對(duì)可空類型做一定判斷,如果存在可空類型的變量操作該對(duì)應(yīng)類的方法,就提示你需要做額外判空處理,這時(shí)候開發(fā)者就根據(jù)提示去做判空處理了,想象下都這樣處理了,你的 Kotlin 代碼還會(huì)出現(xiàn)空指針嗎?(但是有一點(diǎn)很重要就是定義了一個(gè)變量你需要明確它是可空還是非空,如果定義了可空類型你就需要對(duì)它負(fù)責(zé),并且編譯器也會(huì)提示幫助你對(duì)它做額外判空處理)。一起來(lái)看下幾個(gè)例子:
- 非空類型變量或常量不能接收 null 值
- 非空類型的變量或常量中
is(相當(dāng)于java中instanceof)
- 可空類型的變量或常量直接操作相應(yīng)方法會(huì)有明顯的編譯錯(cuò)誤并提示判空操作
然而上面那些都是 Java 給不了你的,所以 Java 程序中一般會(huì)存在三種狀態(tài):一種佛系判空,經(jīng)常會(huì)出現(xiàn)空指針問題。另一種就是一股腦全部判空,可是代碼中充斥著if-else
代碼,可讀性非常差。
最后一種就是非常熟悉程序邏輯以及數(shù)據(jù)流向的開發(fā)者可以正常判斷出哪里需要判空處理,哪里可以不需要,這一種對(duì)開發(fā)者要求極高,因?yàn)槿丝偸菚?huì)犯錯(cuò)的。
4. 可空類型
4.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 為了類型判空處理可算是操碎了心,我們都知道在 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ì)覺得 Kotlin 也許更適合你呢,利用?.鏈?zhǔn)秸{(diào)用的方式把嵌套if-else處理解開了。
4.2 Elvis運(yùn)算符 "?:"
如果 ?: 前面表達(dá)式為 null, 就執(zhí)行 ?: 后面的表達(dá)式,它一般會(huì)和 ?. 一起使用。(注意: 它與Java中的? xxx : xxx 三目運(yùn)算符不一樣)
4.3 安全類型轉(zhuǎn)化運(yùn)算符 as?
如果類型轉(zhuǎn)化失敗就返回null值,否則返回正確的類型轉(zhuǎn)化后的值
val student = person as? Student//相當(dāng)于 if(person is Student) person as Student else null
4.4 非空斷言運(yùn)算符 !! 與 契約(contract)
非空斷言運(yùn)算符!!, 是強(qiáng)制告訴編譯器這個(gè)變量的值不可能null,存在使用風(fēng)險(xiǎn)。一旦存在為 null 直接拋出空指針異常。
很多 Kotlin 開發(fā)者很厭惡這個(gè)操作符,覺得寫起來(lái)不優(yōu)雅很影響代碼的可讀性,關(guān)于如何避免在Kotlin 的代碼中使用 !! 操作符。
其實(shí)是非空斷言的使用場(chǎng)景是存在的,例如你已經(jīng)在一個(gè)函數(shù)中對(duì)某個(gè)變量進(jìn)行判空處理了,但是后面邏輯中再次使用到了它并且你可以確定它不可能為空,可能此時(shí)編譯器無(wú)法識(shí)別它是否是非空,但由于它又是一個(gè)可空類型,那么它又會(huì)提示你進(jìn)行判空處理,很煩人是不?很多人這時(shí)候可能就采用了 !! 確實(shí)缺乏可讀性。
針對(duì)上述問題,除了之前文章中給出解決方案,這次又提供一個(gè)新的解決方案,那就是契約(實(shí)際上就是主動(dòng)告訴編譯器某個(gè)規(guī)則,這樣它就不會(huì)提示做判空處理了) 契約官方正式提出來(lái)是 Kotlin1.3 的版本,雖然還處于 Experimental (比如自定義契約)中,但是實(shí)際上 Kotlin 內(nèi)部代碼,早就使用了契約。一起來(lái)看下內(nèi)置契約是如何解決這個(gè)問題的。
一起來(lái)看內(nèi)置契約的內(nèi)部實(shí)現(xiàn)源碼:
4.5 兼容Java的平臺(tái)類型
通過上述我們可以知道在 Kotlin 中擁有著與 Java 中完全不一樣的類型系統(tǒng)。在 Java 中是不存在所謂的可空類型和非空類型。但是我們都知道 Kotlin 與 Java 的互操性很強(qiáng),幾乎是完全兼容 Java。那么Kotlin是如何兼容Java中的變量類型的呢?
我們?cè)?Kotlin 中肯定需要經(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ù)并沒有寫的這么規(guī)范,顯然無(wú)法通過這種方式完全解決這個(gè)問題。
所以 Kotlin 引入一種新的概念叫做: 平臺(tái)類型,平臺(tái)類型本質(zhì)上就是Kotlin不知道可空性信息的類型,既可以把它當(dāng)做可空類型又可以把它當(dāng)做非空類型。 這就意味要像 Java 代碼中一樣對(duì)在這個(gè)類型上做的操作負(fù)全部責(zé)任。
所以對(duì)于 Java 中函數(shù)參數(shù),Kotlin 去調(diào)用的時(shí)候系統(tǒng)默認(rèn)會(huì)處理可空類型(為了安全性考慮),如果明確了不為空,可以直接把它修改為非空類型,系統(tǒng)也是不為報(bào)編譯錯(cuò)誤的,但是一旦這樣處理了,必須保證不能為空。
那么問題來(lái)了,很多人就疑問出于安全性考慮為什么不直接全部轉(zhuǎn)化可空類型呢? 實(shí)際上這種方案看似可行,實(shí)際上有點(diǎn)不妥,對(duì)于一些明確不可能為空的變量還需要做大量額外的判空操作就顯得冗余。否則非空類型就沒有存在的意義了。
5. 基本數(shù)據(jù)類型和其他基本類型
5.1 基本數(shù)據(jù)類型
我們都知道在 Java 中針對(duì)基本數(shù)據(jù)類型和包裝類型做了區(qū)分。例如一個(gè)基本數(shù)據(jù)類型int
的變量直接存儲(chǔ)了它的值。而一個(gè)引用類型(包裝類型) String
的變量?jī)H僅存儲(chǔ)的是指向該對(duì)象的內(nèi)存地址的引用。基本數(shù)據(jù)類型有著天然的高效存儲(chǔ)以及傳遞的優(yōu)勢(shì),但是不能直接調(diào)用這些類型的方法,而且在Java中集合中不能將它作為泛型實(shí)參類型。
實(shí)際上在Kotlin中并沒有像Java那樣分為了基本數(shù)據(jù)類型和包裝類型,在Kotlin中永遠(yuǎn)是同一種類型。很多人估計(jì)會(huì)問了既然在Kotlin中基本數(shù)據(jù)類型和包裝類型是一樣的,那么是不是意味著Kotlin是使用引用類型來(lái)保存數(shù)據(jù)呢?是不是非常低效呢?不是這樣的,Kotlin在運(yùn)行時(shí)盡量會(huì)把Int
等類型轉(zhuǎn)換成Java中的int
基本數(shù)據(jù)類型,而遇到類似集合或泛型的時(shí)候就會(huì)轉(zhuǎn)化成Java中對(duì)應(yīng)的Integer
等包裝類型。
基本數(shù)據(jù)類型也分為可空類型和非空類型, 具體可參考如下的類型層次結(jié)構(gòu)圖:
5.2 Any 和 Any?類型
Any類型是所有非空類型的超類型,Any?類型則是所有的類型的超類型,即是非空類型的超類型也是所有可空類型的超類型。因?yàn)锳ny?是Any的超類型。具體的層次可參考下面這張圖:
5.3 Unit 類型
Unit 類型也即是 Kotlin 中的空類型,相當(dāng)于 Java 中的 void 類型,默認(rèn)情況下它可以被省略
5.4 Nothing 類型
Nothing類型是所有類型的子類型,它既是所有非空類型的子類型也是所有可空類型的子類型,因?yàn)镹othing是Nothing?的子類型,然而Nothing?又是所有可空類型的子類型。 具體可以看下如下的層次結(jié)構(gòu)圖:
6. 集合和數(shù)組類型
6.1 可變集合與只讀集合之間的區(qū)別和聯(lián)系(以Collection集合為例)
Collection 只讀集合與 MutableCollectio 可變集合區(qū)別:
-
在 Collection 只具有訪問元素的方法,不具有類似 add、remove、clear 之類的方法,而在MutableCollection 中則相比 Collection 多出了修改元素的方法。
-
Collection 只讀集合與 MutableCollectio 可變集合聯(lián)系:
-
MutableCollection 實(shí)際上是 Collection 集合接口的子接口,他們之間是繼承關(guān)系。
6.2 集合之間類的關(guān)系
通過 Collection.kt 文件中可以了解到有這些集合 Iterable(只讀迭代器)和 MutableIterable(可變迭代器)、Collection 和 MutableCollection、List 和 MutableList、Set 和 MutableSet、Map 和 MutableMap。那么它們之間的類關(guān)系圖是怎樣的。
Iterable 和 MutableIterable 接口分別是只讀和可變集合的父接口,Collection 繼承 Iterable 然后 List、Set 接口繼承自 Collection,Map 接口比較特殊它是單獨(dú)的接口,然后MutableMap接口是繼承自 Map.
6.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類和 HashSet 類實(shí)際上 Kotlin 中的 MutableList 和 MutableSet 集合接口的實(shí)現(xiàn)類。把這種關(guān)系加上,上面的類關(guān)系圖可以進(jìn)一步完善。
6.4 集合的初始化
由于在 Kotlin 中集合主要分為了只讀集合和可變集合,那么初始化只讀集合和可變集合的函數(shù)也不一樣。以 List 集合為例,對(duì)于只讀集合初始化一般采用listOf()方法,對(duì)于可變集合初始化一般采用mutableListOf()或者直接創(chuàng)建 ArrayList<E>。
因?yàn)?mutableListOf() 內(nèi)部實(shí)現(xiàn)也是也還是采用創(chuàng)建ArrayList,這個(gè) ArrayList 實(shí)際上是 Java 中的 java.util.ArrayList<E>,只不過在 Kotlin 中使用 typealias (關(guān)于 typealias 的使用之前有過詳細(xì)介紹)取了別名而已。關(guān)于具體內(nèi)容請(qǐng)參考這個(gè)類kotlin.collections.TypeAliasesKt實(shí)現(xiàn)。
6.5 集合使用的注意事項(xiàng)
-
在代碼的任何地方都優(yōu)先使用只讀集合,只在需要修改集合的情況下才去使用可變集合
-
只讀集合不一定是不可變的,關(guān)于這個(gè)只讀和不可變類似于val的只讀和不可變?cè)怼?/strong>
-
不能把一個(gè)只讀類型的集合作為參數(shù)傳遞給一個(gè)帶可變類型集合的函數(shù)。
6.6 平臺(tái)類型的集合轉(zhuǎn)化規(guī)則
正如前面所提及的可空性平臺(tái)類型一樣,Kotlin 中無(wú)法知道可空性信息的類型,既可以把它當(dāng)做可空類型又可以把它當(dāng)做非空類型。集合的平臺(tái)類型和這個(gè)類似,在 Java 中聲明的集合類型的變量也被看做平臺(tái)類型。一個(gè)平臺(tái)類型的集合本質(zhì)上就是可變性未知的集合,Kotlin 中可以把它看做是只讀的集合或者是可變的集合. 實(shí)際上這都不是很重要,因?yàn)槟阒恍枰鶕?jù)你的需求選擇即可,想要執(zhí)行的所有操作都能正常工作,它不像可空性平臺(tái)存在額外判斷操作以及空指針風(fēng)險(xiǎn)。
Tips:可是當(dāng)你決定使用哪一種 Kotlin 類型表示 Java 中集合類型的變量時(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>
.
**Tips:**當(dāng)然上面三種情況可以一種或多種同時(shí)出現(xiàn),那么轉(zhuǎn)化成Kotlin中的集合類型也是多種情況最終重組的類型。
7. 總結(jié)
到這里有關(guān) Kotlin 的類型系統(tǒng)基本就說(shuō)得差不多,該涉及到的內(nèi)容基本都涉及了。其實(shí)仔細(xì)去體會(huì)下為什么 Kotlin 的類型系統(tǒng)要如此設(shè)計(jì),確實(shí)是它一定道理的。我們經(jīng)常聽別人夸 Kotlin 比 Java 優(yōu)點(diǎn)是啥,很多人都說(shuō)少了很多空指針異常,但是為什么能 Kotlin 相比 Java 有更少的空指針異常相信這篇文章也足夠回答你了吧。