Kotlin 知識梳理(7) - Kotlin 的類型系統

Kotlin 知識梳理系列文章

Kotlin 知識梳理(1) - Kotlin 基礎
Kotlin 知識梳理(2) - 函數的定義與調用
Kotlin 知識梳理(3) - 類、對象和接口
Kotlin 知識梳理(4) - 數據類、類委托 及 object 關鍵字
Kotlin 知識梳理(5) - lambda 表達式和成員引用
Kotlin 知識梳理(6) - Kotlin 的可空性
Kotlin 知識梳理(7) - Kotlin 的類型系統
Kotlin 知識梳理(8) - 運算符重載及其他約定
Kotlin 知識梳理(9) - 委托屬性
Kotlin 知識梳理(10) - 高階函數:Lambda 作為形參或返回值
Kotlin 知識梳理(11) - 內聯函數
Kotlin 知識梳理(12) - 泛型類型參數


一、本文概要

本文是對<<Kotlin in Action>>的學習筆記,如果需要運行相應的代碼可以訪問在線環境 try.kotlinlang.org,這部分的思維導圖為:

二、基本數據類型和其它基本類型

2.1 基本類型:Int、Boolean 及其它

Java把基本數據類型和引用類型做了區分:

  • 基本數據類型,例如int的變量直接存儲了它的值,我們不能對這些值調用方法,或者把它們放到集合中。
  • 引用類型的變量存儲的是指向包含該對象的內存地址的引用。

Kotlin不區分基本數據類型和引用類型,它使用的永遠是一個類型(例如Int),此外,你還能對一個數字類型的值調用方法。

在運行時,數字類型會盡可能地使用最高效的方式來表示,大多數情況下,對于變量、屬性、參數和返回類型,KotlinInt類型會被編譯成Java基本數據類型int。唯一不可行的例外是泛型類,例如集合,用作泛型類型參數的基本數據類型會被編譯成對象的Java包類型。

對應到Java基本數據類型的類型完整列表如下:

  • 整數類型:ByteShortIntLong
  • 浮點數類型:FloatDouble
  • 字符類型:Char
  • 布爾類型:Boolean

Int這樣的Kotlin類型在底層可以輕易地編譯成對應的Java基本數據類型。而在Kotlin中使用Java聲明時,Java基本數據類型會變成非空類型,因為它們不能持有null值。

2.2 可空的基本數據類型:Int?、Boolean? 及其它

Kotlin中的可空類型不能用Java的基本數據類型表示,因為null只能被存儲在Java的引用類型的變量中。任何時候,只要使用了基本數據類型的可空版本,它就會被編譯成對應的包裝類型,并且不能比較兩個可空基本數據類型的大小,因為它們之中任何一個都可能為null

除此之外,泛型類是包裝類型應用的另一種情況,如果你 用基本數據類型作為泛型類的類型參數,那么 Kotlin 會使用該類型的包裝形式,例如下面這段代碼,就會創建一個Integer包裝類的列表,盡管你從來沒有指定過可空類型或者用過null值:

val listOfInts = listOf(1, 2, 3)

這是由Java虛擬機實現泛型的方式決定的,JVM不支持用基本數據類型作為類型參數,所以泛型類必須始終使用類型的包裝表示。

2.3 數字轉換

KotlinJava之間一條重要的區別就是處理數字轉換的方式,Kotlin不會自動地把數字從一種類型轉換成另一種,即便是轉換成范圍更大的類型,我們必須 顯示地轉換,對每一種基本數據類型都定義有轉換函數:toByte()toShort()toChar()等,這些函數支持雙向轉換:


在比較裝箱值的時候,比較兩個裝箱值的equals不僅會檢查它們存儲的值,還要比較裝箱類型,也就是說new Integer(42).equals(new Long(42))會返回false

基本數據類型字面值

Kotlin除了支持簡單的十進制數字之外,還支持下面這些在代碼中書簽數字字面值的方式:

  • 使用后綴L表示Long123L
  • 使用標準浮點數表示Double0.121.2e101.2e-10
  • 使用后綴F表示Float123.4f.456F1e3f
  • 使用前綴0x或者0X表示十六進制:0xbcdL
  • 使用前綴0b或者0B表示二進制字面值:0b0001

當你使用數字字面值去初始化一個類型已知的變量時,又或是把字面值作為實參傳給函數時,必要的轉換會自動地發生


此外,算術運算符也被重載了,它們可以接收所有適當的數字類型。

2.4 根類型:Any 和 Any?

Any類型是Kotlin所有非空類型的超類型,包括像Int這樣的基本數據類型,和Java一樣,把基本數據類型的值賦給Any類型的變量會自動裝箱。

Kotlin中,如果你需要可以持有任何可能值的變量,包括null在內,必須使用Any?類型。

在底層,Any類型對應java.lang.ObjectKotlinJava方法參數和返回類型中用到的Object類型看作Any,當Kotlin函數函數中使用Any時,它會被編譯成Java字節碼中的Object

所有的Kotlin類都包含下面三個方法:toStringequalshashCode,這些方法都繼承自AnyAny不能使用其它Object的方法(例如waitnotify),但是可以通過手動把值轉換成java.lang.Object來調用這些方法。

2.5 Unit 類型:Kotlin 的 void

Kotlin中的Unit類型完成了Java中的void一樣的功能,當函數沒有有意思的結果要返回時,它可以用作函數的返回類型:

fun f() : Unit { .. }

Unit是一個完備的類型,可以作為類型參數,而void卻不行。只存在一個值是Unit類型,這個值也叫做Unit,并且(在函數中)會被隱式地返回,當你在重寫返回泛型參數的函數時這非常有用,只需要讓方法返回Unit類型的值:


運行結果為:

2.6 這個函數永不返回:Nothing

對于某些Kotlin函數來說,“返回類型”的概念沒有任何意義,因為它們從來不會成功地結束,Kotlin使用一種特殊的返回類型Nothing來表示:


運行結果為:

Nothing類型沒有任何值,只有被當作函數返回值使用,或者被當作泛型函數返回值的類型參數使用才會有意義,在其它情況下,聲明一個不能存儲任何值的變量沒有任何意義。

返回Nothing的函數可以放在Elvis運算符的右邊來做先決條件檢查:


運行結果為:

三、集合和數組

3.1 可空性和集合

Kotlin 知識梳理(6) - Kotlin 的可空性 中,我們討論了可空類型的概念,但僅僅簡略地談到類型參數的可空性,其實集合也可以持有null元素,和變量可以持有null一樣,類型在被當作類型參數時也可以用同樣的方式來標記。

下面我們創建一個包含可空值的集合,之后遍歷該集合,打印出有效的數字之和以及為null的集合元素個數:


運行結果為:

3.2 只讀集合與可變集合

Kotlin的集合設計與Java不同的另一項重要特質是:它把訪問集合數據的接口和修改集合數據的結構分開了:

  • kotlin.collections.Collection:使用這個接口,可以遍歷集合中的元素、獲取集合大小、判斷集合中是否包含某個元素,執行其他從該集合中讀取數據的操作。
  • kotlin.collections.MutableCollection:修改集合中的數據。

一般的原則是:在代碼的任何地方都應該使用只讀接口,只在代碼需要修改集合的地方使用可變接口的變體

下面的例子演示了如何使用只讀集合和可變集合:



運行結果為:


3.3 Kotlin 集合和 Java

每一個Kotlin接口都是其對應Java集合接口的一個實例,在KotlinJava之間轉移并不需要轉換;不需要包裝器也不需要拷貝數據。

每一種Java集合接口在Kotlin中都有兩種表示:一種是只讀的,另一種是可變的。在下圖當中,可以看出Kotlin集合接口的層級結構,JavaArrayListHashSet都繼承了Kotlin可變接口。


Kotlin中只讀接口和可變接口的基本結構與java.util中的Java集合接口的結構是平行的。可變接口直接對應java.util包中的接口,而它們的只讀版本缺少了所有產生改變的方法。

上圖中包含了Java類中的ArrayListHashSet,在Kotlin看來,它們分別繼承自MutableListMutableSet接口,這樣既得到了兼容性,也得到了可變接口和只讀接口之間清晰的分離。

除了集合之外,KotlinMap類也被表示成了兩種不同的版本:MapMutableMap。我們之前見到的listOf/setOf/mapOf所返回的都是只讀版本。

當你有一個使用java.util.Collection做形參的Java方法,可以把任意CollectionMutableCollection的值作為實參傳遞給這個形參。Java并不會區分只讀集合和可變集合,也就是說即使Kotlin中把集合聲明成只讀的,Java代碼也可以修改這個集合,例如下面的代碼,雖然我們將printInUppercase接收的list參數聲明為只讀的,但是仍然可以通過Java代碼修改它。

//CollectionUtils.java
public class CollectionUtils {
    public static List<String> uppercaseAll(List<String> items) {
        for (int i = 0; i < items.size(); i++) {
            items.set(i, items.get(i).toUpperCase());
        }
        return items;
    }
}

//collections.kt
fun printInUppercase(list : List<String>) {
    println(CollectionUtils.uppercaseAll(list));
    println(list.first())
}

3.4 作為平臺類型的集合

前面我們介紹過,Kotlin把那些定義在Java代碼中的類型看成 平臺類型Kotlin沒有任何關于平臺類型的可空性信息,所以編譯器允許Kotlin代碼將其視為可空或者非空,同樣,Java中聲明的集合類型的變量也被視為平臺類型。

當我們需要重寫或者實現簽名中有集合類型的Java方法時,這些差異才變得重要,我們需要決定使用哪一種Kotlin類型來表示這個Java類型,它們會反映在產生的Kotlin參數類型中:

  • 集合是否為空?
  • 集合中的元素是否為空?
  • 你的方法會不會修改集合?

例如下面這個使用集合參數的Java接口:

interface DataParser<T> {
    void parseData(String input, List<T> output, List<String> errors);
}

我們的選擇為:

  • List<String>將是非空的,因為調用者總是需要接收錯誤信息。
  • 列表中的元素將是可空的,因為不是每個輸出列表中的條目都有關聯的錯誤信息。
  • List<String>將是可變的,因為實現代碼需要向其中添加元素。

那么Kotlin的實現如下:

class PersonParser : DataParser<Person> {
    override fun parseData(input : String, output : MutableList<Person>, 
        errors : MutableList<String?>)
}

3.5 對象和基本數據類型的數組

Kotlin中的一個數組是一個帶有類型參數的類,其元素類型被指定為相應的類型參數,要在Kotlin中創建數組,有下面這些方法供你選擇:

  • arrayOf函數創建一個數組,它包含的元素是指定為該函數的實參
  • arrayOfNulls創建一個給定大小的數組,包含的是null元素,當然,它只能用來創建包含元素類型可空的數組
  • Array構造方法接收數組的大小和一個lambda表達式,調用lambda表達式來創建每一個數組元素,這就是使用非空元素類型來初始化數組,但不用顯示地傳遞每個元素的方式

運行結果為:


創建沒有裝箱的基本數據類型的數組

數組類型的類型參數始終會變成對象類型,因此,如果你聲明了一個Array<Int>,它將會是一個包含裝箱整型的數組,如果你需要創建沒有裝箱的基本數據類型的數組,必須使用一個基本數據類型數組的特殊類。

Kotlin提供了若干個獨立的類,每一種基本數據類型對應一個,例如Int類型值的數組叫作IntArray,要創建一個基本數據類型的數組,有如下的選擇:

  • 該類型的構造方法接收size參數并返回一個使用對應基本數據類型默認值初始化好的數組。
  • 工廠函數(例如IntArrayintArrayOf,以及其他數組類型的函數)接收變長參數的值并創建和存儲這些值的數組。
  • 另一種構造方法,接收一個大小和一個用來初始化每個元素的lambda

運行結果為:



更多文章,歡迎訪問我的 Android 知識梳理系列:

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,316評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,481評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,241評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,939評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,697評論 6 409
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,182評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,406評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,933評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,772評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,973評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,638評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,866評論 1 285
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,644評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,953評論 2 373

推薦閱讀更多精彩內容