關于Kotlin想法的構思2010年誕生于JetBrains。當時,JetBrains已經是許多程序語言開發工具的知名供應商,包括Java、C#、JavaScript、Python、Ruby和PHP。JavaIDE——IntelliJIDEA,Groovy和Scala的插件,都是我們的旗艦產品。
為各種程序語言構建開發工具的經驗給了我們對語言設計領域全面的理解和獨特的觀點。而基于IntelliJ平臺的IDE,包括IntelliJIDEA,仍然是用Java開發的。我們甚至都有點羨慕在.NET團隊中的同事,他們使用C#,一種現代、強大、迅速進化的語言進行開發。但是我們沒有看到任何一種可以用來取代Java的語言。
對于這樣的一門語言我們有哪些要求呢?首要而且最明確的要求就是它必須是靜態類型的。我們想象不到其他任何一種——開發一個擁有數百萬行代碼的代碼庫許多年后——還不把人逼瘋的辦法。其次,我們需要與現有的Java代碼完全兼容。這樣的代碼庫是JetBrains的一筆巨大財富,我們承受不起失去它或是因為互操作性的難度而使其貶值的損失。再次,我們不愿意在工具質量方面接受任何的妥協。開發者的生產力是JetBrains作為一個公司最重要的價值,而強大的工具是達到這一目的的必要條件。最后,我們需要的是一種易于學習和理解的語言。
當看到一個我們公司未能滿足的需要時,我們知道其他公司也處在一個相似的境地,我們希望我們的解決方案能夠在JetBrains之外找到許多用戶。帶著這樣的初心,我們決定走上一條創建一門新語言:Kotlin的道路。事實上,這個項目花費了超出我們預期的時間,在Kotlin1.0最終誕生時,距離第一行代碼提交到代碼庫中已經過去了超過五年;但是現在我們可以確信,這門語言找到了它的受眾并且這些人都留了下來。
Kotlin以靠近俄羅斯圣彼得堡的一座島嶼命名,Kotlin的大部分開發團隊就在那里。在使用島嶼命名這件事上,我們遵循了Java和Ceylon確立的先例,但我們決定選用一處靠近我們家鄉的地方(在英語中,這個名稱通常的發音是“cot-lin”,而不是“coat-lin”或者“caught-lin”)。
在這門語言臨近發布之際,我們意識到一本由參與了語言設計決策人員撰寫的關于Kotlin的書籍是有價值的,他們可以自信地解釋為什么Kotlin中的事物是以它們的方式運行的。本書就是這種努力的結果,我們希望它能幫助你學習和理解Kotlin語言。祝你好運,并愿你一直能愉快地進行開發。
關于本書
《Kotlin實戰》會教你Kotlin編程語言,以及如何使用它構建運行在Java虛擬機和Android平臺的應用程序。這本書開始部分講解了這門語言的基本特性,并逐漸覆蓋更多Kotlin與眾不同的方面,比如它對構建高級抽象和領域特定語言的支持。
這本書很注重將Kotlin與已有的Java工程的集成,幫助你將Kotlin引入到你現在的工作環境。這本書涵蓋了Kotlin1.0,在編寫這本書的同時Kotlin1.1已經在開發過程中了,所以在一些可能的地方,我們提示了1.1版本中做出的更改。但是由于在寫這本書的時候新的版本還沒有完成,我們并不能在書中包含所有的內容。對于進行中的新特性和改變的更新,請參考線上的官方文檔https://kotlinlang.org。
哪些人應該閱讀這本書
《Kotlin實戰》主要面向有一定Java經驗的開發者。Kotlin的構建基于Java中的許多概念和技術,這本書爭取通過使用你現有的知識快速上手。如果你只是剛開始學習Java,又或者你有諸如C#或者JavaScript這些編程語言的經驗,你可能需要參考其他的信息源以理解Kotlin中與JVM交互的那些錯綜復雜的方面,但你還是可以通過這本書學習Kotlin。我們致力于將Kotlin打造成全領域語言,而不是只針對某些特定的問題領域,所以這本書同樣對服務端、Android,以及其他任何以構建基于JVM的工程為目標的開發人員都有用。
這本書是如何組織的
這本書被分成了兩個部分。第1部分解釋了如何開始使用Kotlin現有的庫和API:
第1章講述了Kotlin的關鍵目標、價值和應用的領域,它將向你展示運行Kotlin代碼的所有可能的途徑。
第2章解釋了Kotlin編程的基本元素,包括控制結構、變量和函數聲明。
第3章講解了Kotlin中關于函數聲明的細節并介紹了擴展函數和擴展屬性的概念。
第4章集中在類的聲明上,并介紹了數據類和伴生對象的概念。
第5章介紹了Kotlin中lambda的使用并展示了一些Kotlin標準庫中使用lambda的函數。
第6章描述Kotlin的數據類型系統,并特別關注了可空性和集合的話題。
第2部分會教你如何使用Kotlin構建你自己的API和抽象,并涵蓋了這門語言的一些深層次的特性。
第7章講到了約定原則,它利用特定的名字賦予函數和屬性特殊的意義,還介紹了委托屬性的概念。
第8章展示了如何聲明高階函數——以其他函數作為參數或者返回值的函數,還介紹了內聯函數的概念。
第9章深入探討Kotlin中泛型的話題,先講了基本語法然后是更高級的領域,比如實化類型參數和變型。
第10章包括注解和反射的使用,并以JKid為中心。JKid是大量使用了這些概念的一個小而真實的JSON序列化庫。
第11章介紹了領域特定語言的概念,描述用來構建它的Kotlin工具,并演示了許多DSL示例。
還有三個附錄。附錄A說明了如何用Gradle、Maven和Ant構建Kotlin代碼;附錄B著重于編寫文件注釋和為Kotlin模塊生成API文檔;附錄C是一個探索Kotlin生態圈和發現最新的在線信息的指南。
最好是按順序通讀本書,但是也完全可以只查閱感興趣的包含特定主題的單個章節,在遇到不熟悉的概念的時候再參考其他章節。
第 1 部分 Kotlin 簡介1
1 Kotlin :定義和目的3
1.1 Kotlin 初體驗3
1.2 Kotlin 的主要特征4
1.2.1 目標平臺 :服務器端、Android 及任何 Java 運行的地方4
1.2.2 靜態類型5
1.2.3 函數式和面向對象6
1.2.4 免費并開源7
1.3 Kotlin 應用8
1.3.1 服務器端的 Kotlin8
1.3.2 Android 上的 Kotlin9
1.4 Kotlin 的設計哲學10
1.4.1 務實10
1.4.2 簡潔11
1.4.3 安全12
1.4.4 互操作性13
1.5 使用 Kotlin 工具14
1.5.1 編譯 Kotlin 代碼14
1.5.2 IntelliJ IDEA 和 Android Studio 插件15
1.5.3 交互式 shell15
1.5.4 Eclipse 插件15
1.5.5 在線 playground15
1.5.6 Java 到 Kotlin 的轉換器16
1.6 小結16
2 Kotlin 基礎17
2.1 基本要素 :函數和變量17
2.1.1 Hello,world!18
2.1.2 函數18
2.1.3 變量20
2.1.4 更簡單的字符串格式化 :字符串模板22
2.2 類和屬性23
2.2.1 屬性24
2.2.2 自定義訪問器25
2.2.3 Kotlin 源碼布局 :目錄和包26
2.3 表示和處理選擇 :枚舉和“when”28
2.3.1 聲明枚舉類28
2.3.2 使用“when”處理枚舉類29
2.3.3 在“when”結構中使用任意對象30
2.3.4 使用不帶參數的“when”31
2.3.5 智能轉換 :合并類型檢查和轉換32
2.3.6 重構 :用“when”代替“if”34
2.3.7 代碼塊作為“if”和“when”的分支35
2.4 迭代事物 :“while”循環和“for”循環36
2.4.1 “while”循環36
2.4.2 迭代數字 :區間和數列37
2.4.3 迭代 map38
2.4.4 使用“in”檢查集合和區間的成員39
2.5 Kotlin 中的異常41
2.5.1 “try”“catch”和“finally”41
2.5.2 “try”作為表達式42
2.6 小結44
3 函數的定義與調用45
3.1 在 Kotlin 中創建集合45
3.2 讓函數更好調用47
3.2.1 命名參數48
3.2.2 默認參數值49
3.2.3 消除靜態工具類 :頂層函數和屬性50
3.3 給別人的類添加方法 :擴展函數和屬性53
3.3.1 導入和擴展函數54
3.3.2 從 Java 中調用擴展函數54
3.3.3 作為擴展函數的工具函數55
3.3.4 不可重寫的擴展函數56
3.3.5 擴展屬性58
3.4 處理集合 :可變參數、中綴調用和庫的支持59
3.4.1 擴展 Java 集合的 API59
3.4.2 可變參數 :讓函數支持任意數量的參數60
3.4.3 鍵值對的處理 :中綴調用和解構聲明60
3.5 字符串和正則表達式的處理62
3.5.1 分割字符串62
3.5.2 正則表達式和三重引號的字符串63
3.5.3 多行三重引號的字符串64
3.6 讓你的代碼更整潔 :局部函數和擴展66
3.7 小結68
4 類、對象和接口69
4.1 定義類繼承結構70
4.1.1 Kotlin 中的接口70
4.1.2 open、final 和 abstract 修飾符 :默認為 final72
4.1.3 可見性修飾符 :默認為 public75
4.1.4 內部類和嵌套類 :默認是嵌套類76
4.1.5 密封類 :定義受限的類繼承結構79
4.2 聲明一個帶非默認構造方法或屬性的類80
4.2.1 初始化類 :主構造方法和初始化語句塊80
4.2.2 構造方法 :用不同的方式來初始化父類83
4.2.3 實現在接口中聲明的屬性85
4.2.4 通過 getter 或 setter 訪問支持字段87
4.2.5 修改訪問器的可見性88
4.3 編譯器生成的方法 :數據類和類委托89
4.3.1 通用對象方法89
4.3.2 數據類 :自動生成通用方法的實現92
4.3.3 類委托 :使用“by”關鍵字93
4.4 “object”關鍵字 :將聲明一個類與創建一個實例結合起來95
4.4.1 對象聲明 :創建單例易如反掌95
4.4.2 伴生對象 :工廠方法和靜態成員的地盤98
4.4.3 作為普通對象使用的伴生對象100
4.4.4 對象表達式 :改變寫法的匿名內部類102
4.5 小結104
5 Lambda 編程105
5.1 Lambda 表達式和成員引用105
5.1.1 Lambda 簡介 :作為函數參數的代碼塊106
5.1.2 Lambda 和集合107
5.1.3 Lambda 表達式的語法108
5.1.4 在作用域中訪問變量111
5.1.5 成員引用114
5.2 集合的函數式 API116
5.2.1 基礎 :filter 和 map116
5.2.2 “all”“any”“count”和“find”: 對集合應用判斷式118
5.2.3 groupBy :把列表轉換成分組的 map119
5.2.4 flatMap 和 flatten :處理嵌套集合中的元素120
5.3 惰性集合操作 :序列121
5.3.1 執行序列操作 :中間和末端操作123
5.3.2 創建序列125
5.4 使用 Java 函數式接口126
5.4.1 把 lambda 當作參數傳遞給 Java 方法127
5.4.2 SAM 構造方法 :顯式地把 lambda 轉換成函數式接口129
5.5 帶接收者的 lambda :“with”與“apply”131
5.5.1 “with”函數131
5.5.2 “apply”函數133
5.6 小結135
6 Kotlin 的類型系統137
6.1 可空性137
6.1.1 可空類型138
6.1.2 類型的含義140
6.1.3 安全調用運算符 :“?:”141
6.1.4 Elvis 運算符 :“?:”143
6.1.5 安全轉換 :“as?”145
6.1.6 非空斷言 :“!!”146
6.1.7 “let”函數148
6.1.8 延遲初始化的屬性149
6.1.9 可空類性的擴展151
6.1.10 類型參數的可空性153
6.1.11 可空性和 Java153
6.2 基本數據類型和其他基本類型157
6.2.1 基本數據類型 :Int、Boolean 及其他158
6.2.2 可空的基本數據類型 :Int?、Boolean? 及其他159
6.2.3 數字轉換160
6.2.4 “Any”和“Any?”:根類型162
6.2.5 Unit 類型 :Kotlin 的“void”163
6.2.6 Nothing 類型 :“這個函數永不返回”164
6.3 集合與數組164
6.3.1 可空性和集合165
6.3.2 只讀集合與可變集合167
6.3.3 Kotlin 集合和 Java168
6.3.4 作為平臺類型的集合171
6.3.5 對象和基本數據類型的數組173
6.4 小結175
第 2 部分 擁抱 Kotlin177
7 運算符重載及其他約定179
7.1 重載算術運算符180
7.1.1 重載二元算術運算180
7.1.2 重載復合賦值運算符183
7.1.3 重載一元運算符184
7.2 重載比較運算符186
7.2.1 等號運算符 :“equals"186
7.2.2 排序運算符 :compareTo187
7.3 集合與區間的約定188
7.3.1 通過下標來訪問元素 :“get”和“set”188
7.3.2 “in”的約定190
7.3.3 rangeTo 的約定191
7.3.4 在“for”循環中使用“iterator”的約定192
7.4 解構聲明和組件函數193
7.4.1 解構聲明和循環194
7.5 重用屬性訪問的邏輯 :委托屬性195
7.5.1 委托屬性的基本操作196
7.5.2 使用委托屬性 :惰性初始化和“by lazy()”197
7.5.3 實現委托屬性198
7.5.4 委托屬性的變換規則202
7.5.5 在 map 中保存屬性值203
7.5.6 框架中的委托屬性204
7.6 小結205
8 高階函數 :Lambda 作為形參和返回值207
8.1 聲明高階函數207
8.1.1 函數類型208
8.1.2 調用作為參數的函數209
8.1.3 在 Java 中使用函數類211
8.1.4 函數類型的參數默認值和 null 值212
8.1.5 返回函數的函數214
8.1.6 通過 lambda 去除重復代碼216
8.2 內聯函數 :消除 lambda 帶來的運行時開銷218
8.2.1 內聯函數如何運作219
8.2.2 內聯函數的限制221
8.2.3 內聯集合操作222
8.2.4 決定何時將函數聲明成內聯223
8.2.5 使用內聯 lambda 管理資源223
8.3 高階函數中的控制流225
8.3.1 lambda 中的返回語句 :從一個封閉的函數返回225
8.3.2 從 lambda 返回 :使用標簽返回226
8.3.3 匿名函數 :默認使用局部返回228
8.4 小結229
9 泛型231
9.1 泛型類型參數232
9.1.1 泛型函數和屬性232
9.1.2 聲明泛型類234
9.1.3 類型參數約束235
9.1.4 讓類型形參非空237
9.2 運行時的泛型 :擦除和實化類型參數238
9.2.1 運行時的泛型 :類型檢查和轉換238
9.2.2 聲明帶實化類型參數的函數241
9.2.3 使用實化類型參數代替類引用243
9.2.4 實化類型參數的限制244
9.3 變型 :泛型和子類型化245
9.3.1 為什么存在變型 :給函數傳遞實參245
9.3.2 類、類型和子類型246
9.3.3 協變 :保留子類型化關系248
9.3.4 逆變 :反轉子類型化關系252
9.3.5 使用點變型 :在類型出現的地方指定變型254
9.3.6 星號投影 :使用 * 代替類型參數257
9.4 小結261
10 注解與反射263
10.1 聲明并應用注解264
10.1.1 應用注解264
10.1.2 注解目標265
10.1.3 使用注解定制 JSON 序列化267
10.1.4 聲明注解269
10.1.5 元注解 :控制如何處理一個注解270
10.1.6 使用類做注解參數271
10.1.7 使用泛型類做注解參數272
10.2 反射 :在運行時對 Kotlin 對象進行自省273
10.2.1 Kotlin 反射 API :KClass、KCallable、KFunction 和
KProperty274
10.2.2 用反射實現對象序列化278
10.2.3 用注解定制序列化279
10.2.4 JSON 解析和對象反序列化283
10.2.5 反序列化的最后一步 :callBy() 和使用反射創建對象287
10.3 小結291
11 DSL 構建293
11.1 從 API 到 DSL293
11.1.1 領域特定語言的概念295
11.1.2 內部 DSL296
11.1.3 DSL 的結構297
11.1.4 使用內部 DSL 構建 HTML298
11.2 構建結構化的 API:DSL 中帶接收者的 lambda299
11.2.1 帶接收者的 lambda 和擴展函數類型299
11.2.2 在 HTML 構建器中使用帶接收者的 lambda303
11.2.3 Kotlin 構建器 :促成抽象和重用307
11.3 使用“invoke”約定構建更靈活的代碼塊嵌套310
11.3.1 “invoke”約定 :像函數一樣可以調用的對象310
11.3.2 “invoke”約定和函數式類型311
11.3.3 DSL 中的“invoke”約定 :在 Gradle 中聲明依賴312
11.4 實踐中的 Kotlin DSL314
11.4.1 把中綴調用鏈接起來 :測試框架中的“should”314
11.4.2 在基本數據類型上定義擴展 :處理日期316
11.4.3 成員擴展函數 :為 SQL 設計的內部 DSL317
11.4.4 Anko :動態創建 Android UI320
11.5 小結322
A 構建 Kotlin 項目323
B Kotlin 代碼的文檔化327
譯者序
當收到這本書的翻譯邀請時,我們的內心是激動的,終于有機會將自己喜愛的語言系統地介紹給中國的開發者,而且是通過口碑頗佳的實戰系列。此時,正值2017年度的GoogleI/O召開前夕,接下來重磅消息大家都知道了:在GoogleI/O大會上,Kotlin正式成為了官方的Android開發語言,迅速占據了國內各大技術媒體的頭條。一夜之間,所有的Android開發者都迫切地想搞清楚它的來龍去脈。Kotlin究竟是何方神圣,為什么是它?
這一點兒也不奇怪。對于資深Android開發者來說,Kotlin早已不是新鮮的概念了。早在2015年1月,Android開發者社區大神JakeWharton就發布了一篇使用Kotlin來進行Android開發的總結。那時開始,不少頂尖的開發者和公司就開始嘗試在正式的Android項目中使用Kotlin語言;我們也從2015年開始在多個項目上使用了Kotlin語言。它帶給我們的體驗,和帶給所有其他實踐過Kotlin語言的開發者的一樣:它的發明者JetBrains所言非虛,這是一門簡潔、安全、實用的語言,用了就停不下來,就忍不住地想推薦給周圍的人。我們理所當然地把Kotlin放在了今年第一季度ThoughtWorks技術雷達的評估象限:https://www.thoughtworks.com/radar/languages-and-frameworks/kotlin。
Kotlin讓人愛不釋手的最重要原因就是來自JetBrains的基因。作為最負盛名的IDE創造者,JetBrains深諳開發者的需求,孜孜不倦地追求給開發者提供最實用、最高效的IDE,包括AndroidStudio、IntelliJ、RubyMine等。由這樣想開發者之所想的公司創造出來的語言,又怎么會不受開發者熱捧呢?所以Gradle、Spring,以及越來越多的庫、框架和工具也陸續加入到了支持Kotlin的陣營。
本書深入淺出地介紹了Kotlin語言的方方面面,從最基礎的語言要素到如何定制自己的DSL都有涉及。相信讀者閱讀本書并嘗試之后一定會愛上這門語言,但把Kotlin應用到自己的項目中會不會有什么風險呢?讀者們大可不必擔心,以往的經驗告訴我們,整個過程無縫無痛。首先,Kotlin足夠簡單,對于初學者來說掌握也不算困難,兩三天就可以上手;其次,Kotlin和Java可以無縫地銜接,可以在遺留項目上和Java混用;最后,編譯器的靜態檢查和IDE(必須是JetBrains出品的IntelliJIDEA或者AndroidStudio)強大的輔助功能,可以幫你發現很多問題(例如空指針異常)并將其自動消除在搖籃之中。有的讀者會說,但我還沒有用過這些IDE啊?那你還在猶豫什么,請立即使用它們來提高你的生產力吧!這也算是使用Kotlin帶來的額外收獲。
從Kotlin成為Android開發語言的那一刻開始,我們熱情高漲地投入了幾乎全部業余時間到本書的翻譯工作,終于在最短的時間內把它呈現在廣大讀者面前。這一切還要感謝本書的編輯和所有譯者家人在背后的默默付出。由于譯者水平所限,難免出現謬誤遺漏,還望讀者海涵斧正。
覃宇、羅麗、李思陽、蔣揚海
2017年6月于ThoughtWorks成都
序
當我在2010年春季第一次拜訪JetBrains的時候,我相當確定世界上不需要另一種通用編程語言了。我認為現有的JVM上的語言已經足夠好了,誰會有想法去創建一門新語言呢?在經過大約一個小時的關于大規模代碼庫上產品問題的討論后我被完全說服了,并且后來成為Kotlin一部分的最初想法就已經被描繪在白板上。很快我就加入了JetBrains來主導這門語言的設計與編譯器的開發工作。
到今天,六年多的時光過去了,我們也快要發布第二個版本。我們已經擁有超過30人的團隊和數以千計的活躍用戶,還有很多讓我們難以輕易實現的精彩的設計理念。但是不要擔心,這些想法在進入這門語言之前還必須經過縝密的考察。我們希望這本書的篇幅依然能夠容得下Kotlin的未來。
學習一門編程語言是一個令人興奮而且常常是回報頗豐的嘗試。如果它是你的第一門語言,通過它你能學到整個編程的新世界。如果不是,它會使你以新的術語來思考熟悉的東西,從而以更高層次的抽象來更深入地了解它們。本書主要針對后者,即面向已經熟悉Java的讀者。
從頭開始設計一門語言可能是一項具有挑戰性的任務,但是使其與另一門語言融洽的工作就是另一回事了——尤其是那門語言還包含了許多的憤怒的食人魔,以及一些陰暗的地牢(在這一點上你如果不相信可以去問C++的創造者BjarneStroustrup)。與Java的互操作性(這就是Java與Kotlin之間是如何互相混合調用的)是Kotlin的基石之一,本書也投入了很多的注意力在這一點上。互操作性對于在一個已有的Java代碼庫中逐步地引入Kotlin非常重要。即使從頭開始開發一個新項目時,也必須考慮到能夠將這門語言融入一個擁有更大圖景的平臺中去,而以Java編寫的所有函數庫就是這樣的一個平臺。
當我在編寫本書時,兩個新的目標平臺正在開發:Kotlin現在可以在JavaScript虛擬機上運行以支持全棧web開發,并且還將很快能夠直接編譯成原生代碼,從而在需要的時候能夠脫離任何的虛擬機來運行。1所以,雖然本書是面向JVM的,但是你從中學到的很多東西也是可以應用于其他運行環境的。
本書作者從項目伊始就已經是Kotlin團隊的成員,所以他們對語言本身和內部實現非常熟悉。他們在會議演講、研討會及Kotlin課程方面的經驗使他們能夠對預期的常見問題及可能的陷阱,提供良好的闡述。本書既闡釋了語言特征背后的高級概念,也提供了足夠深入的細節。
希望你能享受與我們的語言及本書相處的時光。正如我經常在我們社區的帖子中說的那樣:使用Kotlin愉快!
ANDREYBRESLAV,JetBrainsKotlin首席設計師