給JAVA程式設計師的Kotlin介紹信
Kotlin已出現一段時間,很多同學都聽過甚至寫過一些demo。在我入門時候總有一種盲人摸象不識大體的感覺,如果恰好你也有這種感覺,那麼就一起探討下面的問題吧:
What is Kotlin?-- Kotlin是什麼?
Why Kotlin?--Kotlin能給我們帶來什麼?
一、Kotlin是什麼
Kotlin是一種 Java系 程式語言。
Java系程式語言指經過編譯生成位元組碼(Java byte code), 從而可在JVM上執行的語言,也可以稱之為Java平臺語言。
注意:Android 中的虛擬機器並不是 JVM,而是Dalvik/ART,需要用位元組碼(Java byte code)進行再一次轉換成相對應的Dalvik位元組碼。
提問:是否可以新創造一門語言,編譯的時候也生成位元組碼,然後在JVM 中執行呢?這樣既能享受到 JVM 和成熟 Java 框架的各種好處,還可以甩掉舊語言的不足之處,有很多自己新的特性!
回答:當然可以 !!比如Java 平臺已經衍生出 Scala、Clojure、Groovy 等比較流行的語言了。而 Kotlin 則是Java平臺系語言中的新星,出自大名鼎鼎的JetBrains 公司。
下面用一個圖來幫助我們瞭解下Java與Kotlin的編譯與執行:

Java VS Kotlin.jpg
名詞解釋(大神請略過)
Java source code :Java原始碼,就是我們根據Java 語言規範所編寫的源程式檔案,副檔名為.java。
Kotlin source code:Kotlin原始碼、就是我們根據Kotlin語言規範所編寫的源程式檔案,副檔名為.kt。
Javac:全稱Java compiler ,是收錄於JDK中的Java語言編譯器。該工具可以將字尾名為.java的原始檔編譯成字尾名為.class的檔案,該檔案包含著可以運行於Java虛擬機器的位元組碼。
Kotlinc:全稱Kotlin compiler 。該工具可以將字尾名為.kt的原始檔編譯成字尾名為.class的檔案,該檔案包含著可以運行於Java虛擬機器的位元組碼。感興趣同學可以繼續研究 Kotlinc用法
可以在Android Studio->File->Settings->Plugins->Kotlin 看到Kotlin外掛,它就是Android開發環境下的Kotlin編譯器。
Java byte codeJava位元組碼,是Java虛擬機器的(JVM)的指令集、儲存於.class檔案中、可以用Javap等命令檢視。
JVM:Java Virtual Machine,縮寫為JVM,一種能夠執行Java bytecode的虛擬機器,JVM遮蔽了與具體作業系統平臺相關的資訊,使得Java程式只需生成在Java虛擬機器上執行的目的碼(位元組碼),就可以在多種平臺上不加修改地執行。
由於JVM並不是專為Java所實現的執行時,所以只要有程式語言的編譯器能生成正確Java bytecode檔案,則這個語言也能實現在JVM上執行,比如我們本文主角Kotlin。
機器碼:Machine code是一種指令集,這種指令集是計算機的CPU可直接解讀懂的資料,比如0000代表載入(LOAD)0001代表儲存(STORE)。
dx工具:Android Apk打包生成的過程,就是將.java檔案轉換成.dex檔案的過程。其中dx.bat就是將.class檔案轉換為.dex檔案的工具,在類似Android\Sdk\build-tools\28.0.3目錄下可以找到。
Dalvik類似JVM,可以稱為Android虛擬機器,字尾為.dex(即“Dalvik Executable”)格式的Java應用程式可以在其上執行。.dex格式是專為Dalvik設計的一種壓縮格式,適合記憶體和處理器速度有限的系統。
ARTAndroid Runtime,在Android 5.0及後續Android版本中作為正式的執行時庫取代了以往的Dalvik虛擬機器。它與Dalvik的主要不同在於採用Ahead-of-time(AOT)技術,那就是在安裝Apk時就把位元組碼轉換為機器碼,Dalvik是在執行時候才把位元組碼轉換為機器碼,所以ART有者更好的執行時效能、但是有著安裝時間更長、佔用儲存空間更大的劣勢。
第一章小結與問題
從上文可以看出創造Kotlin語言最主要工作就是是創造一種新的編譯器Kotlinc,該編譯器主要作用是把符合Kotlin約定語法與結構的原始碼解析為Java位元組碼,從而可以在成熟的Java平臺上執行。
那麼我們這裡留下一些問題給讀者:
Java原始碼、Kotlin原始碼和位元組碼是否是一一對應,為什麼這樣設計?
Java原始碼和Kotlin原始碼是否能夠利用工具實現完全轉換,如果可以原理是什麼,如果不可以,哪些地方不可以轉換,原因是什麼?
二 相對於Java,Kotlin給我們帶來哪些好處
1 Data class -類定義標準與簡化
Data class是Kotlin釋出時候用的標題,其實指類定義的標準與簡化。
例如我們要寫一個學生類,有兩個成員變數 姓名和分數,java和Kotlin程式碼如下:

java class define.png

Kotlin define class.png
上面Java和Kotlin定義作用上是等同的,我們可以看到Kotlin是多麼的簡潔,編譯器直接幫我們補充好、set()、get()、toString()、hashcode()等方法。我們利用工具反編譯Kotlin生成的apk得到,剛才那句話經過編譯器得到是下面的內容(注 只貼了一部分)

Kotlin類反編譯.png

Kotlin反編譯續.png
可以看到Kotlin編譯器自動幫我們生成了建構函式、copy()、get()、set()等方法。
2 Extension Functions-類功能擴充套件
類擴充套件可以幫助我們擴充套件已有的 Class,而無需繼承這個 Class,無論我們能不能訪問原始碼。這對於系統 Class 以及一些第三方 library 中的 Class 特別有幫助。例如

Kotlin Extension Functions.png
我們擴充套件Int 類,讓Int類多出來一個方法getNext(),然後我們在我們的工程裡面都可以使用這個方法。那麼有同學就會想沒有大神把一些常用的擴充套件封裝成庫給我們,恰好Google也做了,Jetpack -這個裡面就包含一些常用的類擴充套件功能、感興趣的同學可以去研究下。
3 Null Safety-空指標檢查
Kotlin 中允許定義變數時可以指定它為可空型別(Nullable Type)和不可空型別(Non-Null Type),預設是不可空型別。

Kotlin null check.png
4 Coroutines-協程
Coroutines:Co -合作+ routine-程式 -》Coroutines=合作的子程式集合,協程的誕生是為了解決子程式間合作與排程的問題。
協程定義理解
子程式間合作問題最主要難點在於非同步與回撥。比如有兩個程式A-下載圖片,B-顯示圖片,流行處理方法就是開啟一個執行緒去下載圖片、下載成功後通知在UI執行緒中程式B執行顯示圖片,這裡我們需要自己寫開啟執行緒、介面回撥程式碼、更不要說去考慮執行緒數量、切換代價等。而有了協程之後,它的內部庫會封裝非同步操作、回撥、訂閱等,使各個子程式在不同執行緒上排程執行,而程式碼則可以寫的保持如同順序執行一樣簡單。
所以我們可以認為協程是一種特殊的子程式集,它可以在一個子程式中中斷,去執行其它子程式,不是函式呼叫,有點類似於 CPU 的中斷。
下面用一幅圖來形象的幫助我們理解程序、執行緒與協程的關係

程序、執行緒、協程.png
形象對映
生產車間:計算機中CPU等硬體資源是有限的,每個CPU單一時間只能執行一個執行緒。所以我們可以把 CUP等硬體資源 當做是 正在生產中的車間 ,車間裡面放置著用於生產的原材料和組裝好的正在進行生產的產線;為了方便理解、假設我們的資源非常有限,只有一個車間、車間內只能開動兩條生產線(雙核CPU)。
車間搭建資源:車間想持續產出,不僅需要著用於生產的原材料、還要有搭建好的產線。車間搭建的資源就是在倉庫中堆積著的原材料、裝置與搭建方案文案、一旦決定生產何種產品,那就從倉庫中取出這這些資源,按照搭建方案把裝置組裝成產線、放置好原材料,通上電開始生產。 車間搭建資源 就相當於 程序 。
每一個車間搭建資源(程序)都是要佔用倉庫不少位置的,所以倉庫越大(儲存容量越大)就可以有更多的程序、對應手機就是儲存越大可以安裝APP更多。
因為不同車間搭建資源只有在裝載在車間中實際生產執行時候才是活動的,所以他們(程序)想進行交流通訊,就是建立一個與車間無關的公共區域、這些車間搭建資源可以在他們執行時候都去該區域放東西或者拿東西,實現資源的交換。
車間選擇(切換):把當前方案所需的原材料拿掉換上新的原材料、把當前產線拆掉,再組裝新的產線。相當於程序切換, 程序切換=原料更換+產線重建 。
產線:每條產線可以通過調整引數、原材料進行不同的產品加工、相當於 執行緒。
產線切換:拆掉原來的產線、根據新的產線方案來重組新的產線,相當於 執行緒切換 ;
場景切換器:控制並實施車間和產線切換的部門,相當於作業系統的Kernel(核心)。我們需要在資源有限的情況下實現利益最大化,但幾條產線的產品具有相互依賴性並且外部對產品的需求一直也在變化中,那麼如何根據外界環境變化最優的安排不同的產線與生產方案,正是kernel考慮的一個很重要問題,排程演算法。
任務控制流:一系列生產任務(使用者新增)並且有內建的排程機制,該機制可以控制決定自己每一個子任務在哪條加工線執行,並且按照順序執行,相當於 協程 。
任務控制流中每個黑色的箭頭代表一個子任務,任務控制流,可以控制子任務暫停執行,開始執行、並可以安排每個子任務在不同生產線上完成,這個相當於 協程切換 ,具體協程切換如下:
1 儲存當前協程的上下文(執行棧,返回地址,暫存器狀態);
2 設定將要喚醒的協程的入口指令地址到IP暫存器;
3 恢復將要喚醒的協程的上下文。
發現與思考
切換代價:程序切換>執行緒切換>協程切換
程序切換相當於重新把當前車間所有東西搬走,按照新的車間搭建方案重新搭建車間和產線,需要搬運、擺放原材料、寫生產小結、重新組裝原材料。
執行緒切換相當於新組裝一個產線,因為新產線需要的各種資源,已經取出來在車間內,所以只需要把不需要的產線拆卸掉,重新組裝新的產線就好,無需搬運生產資料,如果當前產線比較少,甚至不需要拆卸產線,直接組裝新產線就好。
協程切換相當於內部的任務排程,產線無需拆卸重組,只是調整一些引數就可以執行不同的任務, 我們還可以看到協程是自己控制,非作業系統kernel控制。
程序、執行緒、協程限制:車間有限,所以程序有限,在當前只有一個;執行緒在車間內,建立執行緒也需要各種資源、車間大小也有限,所以執行緒也有限;協程,原則上只是一種任務排程機制,大部分以文件形式存在,所需要的資源都是執行緒的,所以限制很少,可以建立很多很多協程。
協程掛起:任務控制流發現它控制的任務A需要的材料不足,那麼它就說先暫停這個子生產任務,但生產線可以繼續工作,把A需要的材料生產出來A再繼續工作。
協程排程:從上面可以知道,協程有兩個重要的部分1 子程式集 2 排程機制。排程機制是協程不同於子程式的關鍵。下面簡單說下協程的排程器:
CoroutineDispatcher,協程排程器,決定協程所在的執行緒或執行緒池。它可以指定協程運行於特定的一個執行緒、一個執行緒池或者不指定任何執行緒(這樣協程就會運行於當前執行緒)。coroutines-core中 CoroutineDispatcher 有三種標準實現Dispatchers.Default、Dispatchers.IO,Dispatchers.Main和Dispatchers.Unconfined,Unconfined 就是不指定執行緒。
launch函式定義如果不指定CoroutineDispatcher或者沒有其他的ContinuationInterceptor,預設的協程排程器就是Dispatchers.Default,Default是一個協程排程器,其指定的執行緒為共有的執行緒池,執行緒數量至少為 2 最大與 CPU 數相同。
協程原理
從上面可以看出,協程主要作用解決不同子程式排程問題,它會封裝非同步操作、回撥、訂閱等,使我們的程式在不同執行緒上排程執行,而程式碼則可以寫的保持如同順序執行一樣。如果這種事是我們自己來做呢?其實我們也很容易想到設定一個全域性的變數,然後不同的協作任務都能改變這個變數的狀態,然後根據變數的狀態值,來決定排程哪一個子程式,這叫Switch狀態機,協程也是這樣做的,總結下就是 狀態機+回撥=協程排程 。感興趣同學可以閱讀協程原理解析
協程與RxJava
功能類似、實現不同,協程寫法更具有可閱讀性,協程具體實現也更高效,感興趣同學可以看搜關鍵字RxJava與協程,多看幾篇。
其他注意事項
頂層方法定義

頂層方法
在kotlin中方法是可以獨立於類的,我們可以像上圖一樣定義方法,然後全域性呼叫,預設方法都是public。
Kotlin lamba
閱讀Kotlin原始碼時候,會遇到lamba表示式, kotlin中對於lambda有很多簡化的約定:
1 如果lambda表示式是函式呼叫的最後一個實參,它可以放在括號外面;
2 當lambda是函式唯一的實參時,可以去掉函式呼叫的括號;
3 如果lambda的引數的型別可以推導,那麼可以省略引數的型別;
4 對於lambda中一個引數時,可以使用預設引數名稱it來代替命名引數,並且lambda的引數列表可以簡化,省略引數列表和->。
第二章小結與Kotlin啟示
Kotlin語言為解決Java語言的問題而生,它提供了類的標準定義與簡化、類的方法擴充套件、空指標檢查、協程等使用者(工程師)友好的功能,這些都是把使用者經常需要做和必須做的工作抽取出來提供標準化和自動化的解決方案,從而減少使用者的工作量,這屬於AI思維。
AI思維是通過把重複、規律的事交給人工智慧來成降低成本,本質是標準化和量化思維!當一件事情可以標準化和量化,我們便能夠實現可複製的成功。
參考文獻
Kotlin中文學習網站 http://www.kotlincn.net/docs/reference/