第七色在线视频,2021少妇久久久久久久久久,亚洲欧洲精品成人久久av18,亚洲国产精品特色大片观看完整版,孙宇晨将参加特朗普的晚宴

首頁 慕課教程 Kotlin 教程 Kotlin 教程 Kotlin 類型系統(tǒng)

Kotlin類型系統(tǒng)

這篇文章一起來看下 Kotlin 中類型系統(tǒng),其中涉及到一個很重要的概念就是大家常說的可空性以及為什么 Kotlin 相比 Java 在一定程度上能降低空指針異常。此外在 Kotlin 中完全采用和 Java 不同思路來定義它的類型系統(tǒng)。也正因為這樣類型系統(tǒng)天然具有讓 Kotlin 在空指針異常出現(xiàn)的頻率明顯低于 Java出現(xiàn)的頻率的優(yōu)勢。此外 Kotlin 考慮使用和 Java 完全不同類型系統(tǒng),以及它是如何去做到極大兼容和互操作。

1. 梳理概念

在學習 Kotlin 類型系統(tǒng)之前,我們不妨先一起來思考以下幾個概念,如果不明確這幾個概念很難從根本上去理解 Kotlin 類型系統(tǒng),以及 Kotlin 在類型系統(tǒng)方面為什么優(yōu)于 Java。

1.1 類型的本質

類型本質是什么呢? 為什么變量擁有類型? 這兩個問題在維基百科上給出了很好的回答:

類型實際上就是對數(shù)據(jù)的分類,決定了該類型上可能的值以及該類型的值上可以完成的操作。 需要特別去注意一下后面的闡述: “該類型上可能的值以及該類型的值上可以完成的操作。” 因為Java 的類型系統(tǒng)其實并沒有 100% 符合這個規(guī)則,所以這也是 Java 類型系統(tǒng)所存在的問題,下面會做出具體的分析。

1.2 類與類型

關于 和 **類型 **估計很多開發(fā)者往往忽略它們之間的區(qū)別,因為在真正的應用場景并不會區(qū)分這么細。我們在使用中往往會把類等同于類型,實際上是完全不同兩個東西。其實在 Java 中也有體現(xiàn),例如 List<String>、Lis<Integer>List,對于前者 List<String>List<Integer> 只能是類型不能說是類,而對于 List 它既可以是 List 類也可以是類型(Java 中的原生類型)。

其實在 Kotlin 則把這個概念提升到一個更高的層次,因為 Kotlin 中每個類多了一個可空類型,例如String類就對應兩種類型 String 類型和 String? 可空類型。而在 Java 中除了泛型類型,每個類只對應一種類型(就是類的本身),所以往往被忽略。

我們可以把 Kotlin 中的類可分為兩大類(Java 也可以這樣劃分): 泛型類非泛型類。

非泛型類

先說非泛型類也就是開發(fā)中接觸最多的一般類,一般的類去定義一個變量的時候,它的實際就是這個變量的類型。例如:

var msg: String 這里我們可以說Stringmsg 變量的類型是一致的。但是在 Kotlin 中還有一種特殊的類型那就是可空類型,可以定義為var msg: String?,這里的Stringmsg變量的String?類型就不一樣了。所以在 Kotlin 中一個一般至少對應兩種類型. 所以類和類型不是一個東西。

泛型類

泛型類比非泛型類要更加復雜,實際上一個泛型類可以對應無限種類型。為什么這么說,其實很容易理解。我們從前面文章知道,在定義泛型類的時候會定義泛型形參,要想拿到一個合法的泛型類型就需要在外部使用地方傳入具體的類型實參替換定義中的類型形參。我們知道在 Kotlin 中 List 是一個類,它不是一個類型。由它可以衍生成無限種泛型類型例如List<String>、List<Int>、List<List<String>>、List<Map<String,Int>>。

1.3 子類、子類型與超類、超類型

我們一般說子類就是派生類,該類一般會繼承它的超類。例如: class Student: Person(),這里的Student一般稱為Person的子類, PersonStudent的超類。

子類型和超類型定義則完全不一樣,我們從上面類和類型區(qū)別就知道一個類可以有很多類型,那么子類型不僅僅是想子類那樣繼承關系那么嚴格。

子類型定義的規(guī)則一般是這樣的: 任何時候如果需要的是A類型值的任何地方,都可以使用B類型的值來替換的,那么就可以說B類型是A類型的子類型或者稱A類型是B類型的超類型。可以明顯看出子類型的規(guī)則會比子類規(guī)則更為寬松。那么我們可以一起分析下面幾個例子:

圖片描述

Tips:某個類型也是它自己本身的子類型,很明顯 Person 類型的值任意出現(xiàn)地方,Person 肯定都是可以替換的。屬于子類關系的一般也是子類型關系。像String類型值肯定不能替代Int類型值出現(xiàn)的地方,所以它們不存在子類型關系

再來看個例子,所有類的非空類型都是該類對應的可空類型的子類型,但是反過來說就不行,就比如Person非空類型是Person?可空類型的子類型,很明顯嘛,任何Person?可空類型出現(xiàn)值的地方,都可以使用Person非空類型的值來替換。

其實這些我在開發(fā)過程中是可以體會得到的,比如細心的同學就會發(fā)現(xiàn),我們在 Kotlin 開發(fā)過程,如果一個函數(shù)接收的是一個可空類型的參數(shù),調(diào)用的地方傳入一個非空類型的實參進去是合法的。但是如果一個函數(shù)接收的是非空類型參數(shù),傳入一個可空類型的實參編譯器就會提示你,可能存在空指針問題,需要做非空判斷。 因為我們知道非空類型比可空類型更安全。來幅圖理解下:

圖片描述

2. Java類型系統(tǒng)存在NPE的本質原因

有了上述關于類型本質的闡述,我們一起來看下 Java 中的一些基本類型來套用類型本質的定義,來看看有什么問題。

使用類型的定義驗證int類型

例如一個 int 類型的變量,那么表明它只能存儲 int 類型的數(shù)據(jù),我們都知道它用4個字節(jié)存儲,數(shù)值表示范圍是-2147483648 ~ 2147483647,那么規(guī)定該類型可能存在的值,然后我們可以對該類型的值進行運算操作。似乎沒毛病,int類型和類型本質闡述契合的是如此完美。

但是String類型呢?也是這樣的嗎?請接著往下看:

使用類型的定義驗證String類型或其他定義類對應的類型

例如一個 String 類型的變量,在 Java 中它卻可以存在兩種值:一個是String類的實例另一種則是null。然后我們可以對這些值進行一些操作,第一種String類實例當然允許你調(diào)用String類所有操作方法,但是對于第二種null值,操作則非常有限,如果你強行使用null值去操作String類中的操作方法,那么恭喜你,你將獲得一個NullPointerException空指針異常。

在 Java 中為了程序的健壯性,這就要求開發(fā)者對 String 類型的值還得需要做額外的判斷,然后再做相應的處理,如果不做額外判斷處理那么就很容易得到空指針異常。

這就出現(xiàn)同一種類型變量存在多種值,卻不能得到平等一致的對待。對比上述 int 類型的存在的值都是一致對待,所有該類型上所有可能的值都可以進行相同的運算操作。下面接著看著一個很有趣例子:

圖片描述

貌似連 Java 中的instanceof都不承認null是一個String類型的值。這兩種值的操作也完全不一樣:真實的String允許你調(diào)用它的任何方法,而null值只允許非常有限的操作。那么 Kotlin 類型系統(tǒng)是如何解決這樣的問題的呢? 請接著往下看。

3. Kotlin類型系統(tǒng)如何解決問題

Java 中的類型系統(tǒng)中String類型或其他自定義類的類型,貌似和類型本質定義不太符合,該類型的所有可能值卻被區(qū)別對待,存在二義性。還得額外判斷,直接問題就是給開發(fā)者帶來了額外負擔得做非空判斷,一旦處理不好就會出現(xiàn)空指針導致程序崩潰。這就是Java中引發(fā)空指針問題的本質。

抓住問題的本質,Kotlin 做一個很偉大的舉措那就是類型的拆分,將 Kotlin 中所有的類型拆分成兩種:一種是非空類型,另一種則是可空類型;其中非空類型變量不允許null值的賦值操作,換句話說就是String非空類型只存在String類的實例不存在null值,所以針對String非空類型的值你可以大膽使用String類所有相關方法,不存在二義性。

當然也會存在 null 情況,那就可以使用可空類型,在使用可空類型的變量的時候編譯器在編譯時期會做針對可空類型做一定判斷,如果存在可空類型的變量操作該對應類的方法,就提示你需要做額外判空處理,這時候開發(fā)者就根據(jù)提示去做判空處理了,想象下都這樣處理了,你的 Kotlin 代碼還會出現(xiàn)空指針嗎?(但是有一點很重要就是定義了一個變量你需要明確它是可空還是非空,如果定義了可空類型你就需要對它負責,并且編譯器也會提示幫助你對它做額外判空處理)。一起來看下幾個例子:

  • 非空類型變量或常量不能接收 null 值

圖片描述

  • 非空類型的變量或常量中is(相當于java中instanceof)

圖片描述

  • 可空類型的變量或常量直接操作相應方法會有明顯的編譯錯誤并提示判空操作

圖片描述

然而上面那些都是 Java 給不了你的,所以 Java 程序中一般會存在三種狀態(tài):一種佛系判空,經(jīng)常會出現(xiàn)空指針問題。另一種就是一股腦全部判空,可是代碼中充斥著if-else代碼,可讀性非常差。

最后一種就是非常熟悉程序邏輯以及數(shù)據(jù)流向的開發(fā)者可以正常判斷出哪里需要判空處理,哪里可以不需要,這一種對開發(fā)者要求極高,因為人總是會犯錯的。

4. 可空類型

4.1 安全調(diào)用運算符 “?.”

?.相當于判空處理,如果不為 null 就執(zhí)行 ?. 后面的表達式,否則就返回 null

text?.substring(0,2) //相當于 if(text != null) text.substring(0,2) else null

其實 Kotlin 為了類型判空處理可算是操碎了心,我們都知道在 Java 中做判空處理無非就是if-else? xxx : xxx三目運算符來實現(xiàn)。

但是有時候出現(xiàn)嵌套判空的時候整個代碼就是一個“箭頭”,可讀性就很差了。由以上例子可知?.if-else省了很多代碼,這還無法完全顯露它的優(yōu)點,下面這個例子就更加明顯了。

Java中的if-else 嵌套處理

圖片描述

Kotlin中的安全調(diào)用運算符?.鏈式調(diào)用處理

圖片描述

對比兩種方式的實現(xiàn)你會不會覺得 Kotlin 也許更適合你呢,利用?.鏈式調(diào)用的方式把嵌套if-else處理解開了。

4.2 Elvis運算符 "?:"

如果 ?: 前面表達式為 null, 就執(zhí)行 ?: 后面的表達式,它一般會和 ?. 一起使用。(注意: 它與Java中的? xxx : xxx 三目運算符不一樣)

圖片描述

4.3 安全類型轉化運算符 as?

如果類型轉化失敗就返回null值,否則返回正確的類型轉化后的值

val student = person as? Student//相當于 if(person is Student) person as Student else null

4.4 非空斷言運算符 !!契約(contract)

非空斷言運算符!!, 是強制告訴編譯器這個變量的值不可能null,存在使用風險。一旦存在為 null 直接拋出空指針異常。

很多 Kotlin 開發(fā)者很厭惡這個操作符,覺得寫起來不優(yōu)雅很影響代碼的可讀性,關于如何避免在Kotlin 的代碼中使用 !! 操作符。

其實是非空斷言的使用場景是存在的,例如你已經(jīng)在一個函數(shù)中對某個變量進行判空處理了,但是后面邏輯中再次使用到了它并且你可以確定它不可能為空,可能此時編譯器無法識別它是否是非空,但由于它又是一個可空類型,那么它又會提示你進行判空處理,很煩人是不?很多人這時候可能就采用了 !! 確實缺乏可讀性。

針對上述問題,除了之前文章中給出解決方案,這次又提供一個新的解決方案,那就是契約(實際上就是主動告訴編譯器某個規(guī)則,這樣它就不會提示做判空處理了) 契約官方正式提出來是 Kotlin1.3 的版本,雖然還處于 Experimental (比如自定義契約)中,但是實際上 Kotlin 內(nèi)部代碼,早就使用了契約。一起來看下內(nèi)置契約是如何解決這個問題的。

圖片描述

一起來看內(nèi)置契約的內(nèi)部實現(xiàn)源碼:

圖片描述

4.5 兼容Java的平臺類型

通過上述我們可以知道在 Kotlin 中擁有著與 Java 中完全不一樣的類型系統(tǒng)。在 Java 中是不存在所謂的可空類型和非空類型。但是我們都知道 Kotlin 與 Java 的互操性很強,幾乎是完全兼容 Java。那么Kotlin是如何兼容Java中的變量類型的呢?

我們在 Kotlin 中肯定需要經(jīng)常調(diào)用 Java 代碼,有的人可能會回答說 Java 中使用@NotNull和@Nullable注解來標識。確實 Kotlin 可以識別多種不同風格的注解,包括 javax.annotation、android.support.annotation、org.jetbrains.annotation等。但是一些之前的第三方庫并沒有寫的這么規(guī)范,顯然無法通過這種方式完全解決這個問題。

所以 Kotlin 引入一種新的概念叫做: 平臺類型,平臺類型本質上就是Kotlin不知道可空性信息的類型,既可以把它當做可空類型又可以把它當做非空類型。 這就意味要像 Java 代碼中一樣對在這個類型上做的操作負全部責任。

所以對于 Java 中函數(shù)參數(shù),Kotlin 去調(diào)用的時候系統(tǒng)默認會處理可空類型(為了安全性考慮),如果明確了不為空,可以直接把它修改為非空類型,系統(tǒng)也是不為報編譯錯誤的,但是一旦這樣處理了,必須保證不能為空。

圖片描述

那么問題來了,很多人就疑問出于安全性考慮為什么不直接全部轉化可空類型呢? 實際上這種方案看似可行,實際上有點不妥,對于一些明確不可能為空的變量還需要做大量額外的判空操作就顯得冗余。否則非空類型就沒有存在的意義了。

5. 基本數(shù)據(jù)類型和其他基本類型

5.1 基本數(shù)據(jù)類型

我們都知道在 Java 中針對基本數(shù)據(jù)類型和包裝類型做了區(qū)分。例如一個基本數(shù)據(jù)類型int的變量直接存儲了它的值。而一個引用類型(包裝類型) String的變量僅僅存儲的是指向該對象的內(nèi)存地址的引用?;緮?shù)據(jù)類型有著天然的高效存儲以及傳遞的優(yōu)勢,但是不能直接調(diào)用這些類型的方法,而且在Java中集合中不能將它作為泛型實參類型。

實際上在Kotlin中并沒有像Java那樣分為了基本數(shù)據(jù)類型和包裝類型,在Kotlin中永遠是同一種類型。很多人估計會問了既然在Kotlin中基本數(shù)據(jù)類型和包裝類型是一樣的,那么是不是意味著Kotlin是使用引用類型來保存數(shù)據(jù)呢?是不是非常低效呢?不是這樣的,Kotlin在運行時盡量會把Int等類型轉換成Java中的int基本數(shù)據(jù)類型,而遇到類似集合或泛型的時候就會轉化成Java中對應的Integer等包裝類型。

基本數(shù)據(jù)類型也分為可空類型和非空類型, 具體可參考如下的類型層次結構圖:
圖片描述

5.2 Any 和 Any?類型

Any類型是所有非空類型的超類型,Any?類型則是所有的類型的超類型,即是非空類型的超類型也是所有可空類型的超類型。因為Any?是Any的超類型。具體的層次可參考下面這張圖:

圖片描述

5.3 Unit 類型

Unit 類型也即是 Kotlin 中的空類型,相當于 Java 中的 void 類型,默認情況下它可以被省略

5.4 Nothing 類型

Nothing類型是所有類型的子類型,它既是所有非空類型的子類型也是所有可空類型的子類型,因為Nothing是Nothing?的子類型,然而Nothing?又是所有可空類型的子類型。 具體可以看下如下的層次結構圖:

圖片描述

6. 集合和數(shù)組類型

6.1 可變集合與只讀集合之間的區(qū)別和聯(lián)系(以Collection集合為例)

Collection 只讀集合與 MutableCollectio 可變集合區(qū)別:

  • 在 Collection 只具有訪問元素的方法,不具有類似 add、remove、clear 之類的方法,而在MutableCollection 中則相比 Collection 多出了修改元素的方法。

  • Collection 只讀集合與 MutableCollectio 可變集合聯(lián)系:

  • MutableCollection 實際上是 Collection 集合接口的子接口,他們之間是繼承關系。

圖片描述

6.2 集合之間類的關系

通過 Collection.kt 文件中可以了解到有這些集合 Iterable(只讀迭代器)和 MutableIterable(可變迭代器)、Collection 和 MutableCollection、List 和 MutableList、Set 和 MutableSet、Map 和 MutableMap。那么它們之間的類關系圖是怎樣的。

Iterable 和 MutableIterable 接口分別是只讀和可變集合的父接口,Collection 繼承 Iterable 然后 List、Set 接口繼承自 Collection,Map 接口比較特殊它是單獨的接口,然后MutableMap接口是繼承自 Map.

圖片描述

6.3 Java 中的集合與 Kotlin 中集合對應關系

我們剛剛說到在 Kotlin 中集合的設計與Java不一樣,但是每一個 Kotlin 的接口都是其對應的 Java 集合接口的一個實例,也就是在 Kotlin 中集合與 Kotlin 中的集合存在一定的對應關系。Java 中的 ArrayList類和 HashSet 類實際上 Kotlin 中的 MutableList 和 MutableSet 集合接口的實現(xiàn)類。把這種關系加上,上面的類關系圖可以進一步完善。

圖片描述

6.4 集合的初始化

由于在 Kotlin 中集合主要分為了只讀集合和可變集合,那么初始化只讀集合和可變集合的函數(shù)也不一樣。以 List 集合為例,對于只讀集合初始化一般采用listOf()方法,對于可變集合初始化一般采用mutableListOf()或者直接創(chuàng)建 ArrayList<E>。

因為 mutableListOf() 內(nèi)部實現(xiàn)也是也還是采用創(chuàng)建ArrayList,這個 ArrayList 實際上是 Java 中的 java.util.ArrayList<E>,只不過在 Kotlin 中使用 typealias (關于 typealias 的使用之前有過詳細介紹)取了別名而已。關于具體內(nèi)容請參考這個類kotlin.collections.TypeAliasesKt實現(xiàn)。

6.5 集合使用的注意事項

  • 在代碼的任何地方都優(yōu)先使用只讀集合,只在需要修改集合的情況下才去使用可變集合

  • 只讀集合不一定是不可變的,關于這個只讀和不可變類似于val的只讀和不可變原理。

  • 不能把一個只讀類型的集合作為參數(shù)傳遞給一個帶可變類型集合的函數(shù)。

6.6 平臺類型的集合轉化規(guī)則

正如前面所提及的可空性平臺類型一樣,Kotlin 中無法知道可空性信息的類型,既可以把它當做可空類型又可以把它當做非空類型。集合的平臺類型和這個類似,在 Java 中聲明的集合類型的變量也被看做平臺類型。一個平臺類型的集合本質上就是可變性未知的集合,Kotlin 中可以把它看做是只讀的集合或者是可變的集合. 實際上這都不是很重要,因為你只需要根據(jù)你的需求選擇即可,想要執(zhí)行的所有操作都能正常工作,它不像可空性平臺存在額外判斷操作以及空指針風險。

Tips:可是當你決定使用哪一種 Kotlin 類型表示 Java 中集合類型的變量時,需要考慮以下三種情況:

  • 1、集合是否為空?

如果為空轉換成Kotlin中集合后面添加 ?,例如Java中的List<String>轉化成Kotlin中的List<String>?

  • 2、集合中的元素是否為空?

如果為空轉換成Kotlin中集合泛型實參后面添加 ?,例如Java中的List<String>轉化成Kotlin中的List<String?>

  • 3、操作方法會不會修改集合?(集合的只讀或可變)

如果是只讀的,例如Java中的List<String>轉化成Kotlin中的List<String>;如果是可變的,例如Java中的List<String>轉化成Kotlin中的MutableList<String>.

**Tips:**當然上面三種情況可以一種或多種同時出現(xiàn),那么轉化成Kotlin中的集合類型也是多種情況最終重組的類型。

7. 總結

到這里有關 Kotlin 的類型系統(tǒng)基本就說得差不多,該涉及到的內(nèi)容基本都涉及了。其實仔細去體會下為什么 Kotlin 的類型系統(tǒng)要如此設計,確實是它一定道理的。我們經(jīng)常聽別人夸 Kotlin 比 Java 優(yōu)點是啥,很多人都說少了很多空指針異常,但是為什么能 Kotlin 相比 Java 有更少的空指針異常相信這篇文章也足夠回答你了吧。