hello Kotlin

1.1 Kotlin的身世

  • 寫了許久 Java,有沒有發現其實你寫了太多冗餘的代碼?

  • 後來你體驗了一下 Python,有沒有覺得不寫分號的感覺真是超級爽?

  • 你雖然勤勤懇懇,可到頭來卻被 NullPointerException 折磨的死去活來,難道就沒有受夠這種日子麼?

    width="728" height="90" frameborder="0" marginwidth="0" marginheight="0" vspace="0" hspace="0" allowtransparency="true" scrolling="no" allowfullscreen="true" id="aswift_0" name="aswift_0" style="box-sizing: border-box; border-width: 0px; border-style: initial; margin: 0px; padding: 0px; left: 0px; position: absolute; top: 0px;">

  • 直到有一天你發現自己已經寫了好幾十萬行代碼,發現居然全是 getter 和 setter!

blob.png

哈哈,實際上你完全可以不用這麼痛苦,用 Kotlin 替代 Java 開發你的程序,無論是 Android 還是 Server,你都能像之前寫 Java 一樣思考,同時又能享受到新一代編程語言的特性,說到這裏你是不是開始心動了呢?下面我就通過這篇文章來給大家介紹一下 Kotlin 究竟是何方神聖。

話說,Kotlin 是 JetBrain 公司搞出來的,運行在 JVM 上的一門靜態類型語言,它是用波羅的海的一個小島的名字命名的。從外觀上,乍一看還以爲是 Scala,我曾經琢磨着把 Scala 作爲我的下一門語言,不過想想用 Scala 來幹嘛呢,我又不做大數據,而它又太複雜了o(╯□╰)o

用Kotlin創建一個數據類

blob.png

最初是在 intelliJ 的源碼中看到 Kotlin 的,那時候 Kotlin 的版本還不太穩定,所以源碼總是編譯不過,真是要抓狂啊,還罵『什麼破玩意兒!爲什麼又出來新語言了?Groovy 還沒怎麼學會,又來個 Kotlin!』話說,Kotlin,難道是『靠它靈』的意思??

其實經過一年多的發展,Kotlin 1.0已經 release,feature 基本完善,api 也趨於穩定,這時候嘗試也不會有那種被坑的感覺了。過年期間也算清閒,於是用 Kotlin 做了個 app,簡單來說,就是幾個感覺:

  • 思路與寫 Java 時一樣,不過更簡潔清爽

  • 少了冗餘代碼的煩惱,更容易專注於功能的開發,整個過程輕鬆愉快

  • 擴展功能使得代碼寫起來更有趣

  • 空安全和不可變類型使得開發中對變量的定義和初始化傾注了更多關注

  • 啊啊,我再也不用寫那個 findViewById 了,真的爽爆有木有!

1.2 第一個Kotlin程序

Kotlin 開發當然使用 JetBrain 系列的 IDE,實際上 intelliJ idea 15 發佈時就已經內置了 Kotlin 插件,更早的版本則需要到插件倉庫中下載安裝 Kotlin 插件——在安裝時你還會看到有個 Kotlin Extensions for Android,不要管他,已經過時了。安裝好以後,我們就可以使用 Kotlin 進行開發了。

接下來我們用 Android Studio 創建一個 Android 工程,比如叫做 HelloKotlin,在 app 目錄下面的 build.gradle 文件中添加下面的配置:

blob.png

這裏添加了 Kotlin 對 Android 的擴展,同時也添加了 Kotlin 的 Gradle 插件。

接下來就可以編寫 Kotlin 代碼了——等等,Android Studio 會幫我們生成一個MainActivity,你可以直接在菜單

Code -> Convert Java file to Kotlin file

將這個 Java 代碼轉換爲 Kotlin 代碼。截止到現在,你什麼都不用做,程序就已經可以跑起來了。

2、完美爲Java開發者打造

2.1 通用的集合框架

我們都知道 Jvm 上面的語言,像什麼 Java、Groovy、Jython 啥的,都是要編成虛擬機的字節碼的,一旦編成字節碼,在一定程度上大家就都平等了。

英雄不問出身啊

有人做過一個非常形象的比喻:Java 虛擬機語言就是打羣架。Kotlin 正是充分利用了這一點,它自己的標準庫只是基於 Java 的語言框架做了許多擴展,你在Kotlin 當中使用的集合框架仍然跟你在Java當中一樣。

舉個例子,如果你想要在 Kotlin 中使用 ArrayList,很簡單,Java 的 ArrayList 你可以隨意使用,這個感覺跟使用 Java 沒有任何區別,請看:

blob.png

然,Kotlin 標準庫也對這些做了擴展,我們在享用 Java 世界的一切資源的同時,還能比原生 Java 代碼更滋潤,真是爽爆有木有:

blob.png

2.2 與Java交互

Kotlin 的標準庫更多的是對 Java 庫的擴展,基於這個設計思路,你絲毫不需要擔心 Kotlin 對 Java 代碼的引用,你甚至可以在 Kotlin 當中使用 Java 反射,反正只要是 Java 有的,Kotlin 都有,於是有人做出這樣的評價:

Kotlin 就是 Java 的一個擴展

這樣說 Kotlin 顯然是不公平的,但就像微信剛面世那會兒要爲 QQ 接收離線消息一樣,總得抱幾天大腿嘛。

有關從 Kotlin 中調用Java的官方文檔在此Calling Java code from Kotlin (https://kotlinlang.org/docs/reference/java-interop.html#static-methods-and-fields),其中最常見的就是 Getter / Setter 方法對應到 Kotlin 屬性的調用,舉個例子:

準備一個Java類

blob.png

下面是Kotlin代碼

blob.png

所以我們在 Android 開發時,就可以這樣:

  1. view.background = ...
  2. textView.text = ...

反過來在 Java 中調用 Kotlin 也毫無壓力,官方文檔C alling Kotlin from Java 對於常見的情況作了比較詳細的闡述,這裏就不再贅述。

3、簡潔,可靠,有趣

3.1 數據類

最初學 Java 的時候,學到一個概念叫 JavaBean,當時就要被這個概念給折磨死了。明明很簡單的一個東西,結果搞得很複雜的樣子,而且由於當時對於這些數據類的設計概念不是很清晰,因而也並不懂得去覆寫諸如 equals 和 hashcode 這樣重要的方法,一旦用到 HashMap 這樣的集合框架,總是出了問題都不知道找誰。

Kotlin 提供了一種非常簡單的方式來創建這樣的數據類,例如:

  1. data class Coordinate(val x: Double, val y: Double)

僅僅一行代碼,Kotlin 就會創建出一個完整的數據類,並自動生成相應的 equals、hashcode、toString 方法。是不是早就受夠了 getter和setter?反正我是受夠了。

3.2 空安全與屬性代理

第一次見到空類型安全的設計是在 Swift 當中,那時候還覺得這個東西有點兒意思哈,一旦要求變量不能爲空以後,因它而導致的空指針異常的可能性就直接沒有了。想想每次 QA 提的 bug 吧,說少了都得有三分之一是空指針吧。

Kotlin 的空安全設計,主要是在類型後面加?表示可空,否則就不能爲 null。

  1. val anInt: Int = null // 錯誤
  2. val anotherInt: Int? = null // 正確

使用時,則:

blob.png

而對於 Java 代碼,比如我們在覆寫 Activity 的 onCreate 方法時,有個參數 savedInstanceState:

  1. override fun onCreate(savedInstanceState: Bundle!)

這表示編譯器不再強制 savedInstanceState 是否可 null,開發者在覆寫時可以自己決定是否可 null。當然,對於本例,onCreate 的參數是可能爲 null 的,因此覆寫以後的方法應爲:

  1. override fun onCreate(savedInstanceState: Bundle?)

通常來講,教科書式的講法,到這裏就該結束了。然而直到我真正用 Kotlin 開始寫代碼時,發現,有些需求實現起來真的有些奇怪。

還是舉個例子,我需要在 Activity 當中創建一個 View 的引用,通常我們在 Java 代碼中這麼寫:

blob.png

在 Kotlin 當中呢?

blob.png

每次用 aTextView 都要加倆!,不然編譯器不能確定它究竟是不是 null,於是不讓你使用。。這尼瑪。。。到底是爲了方便還是爲了麻煩??

所以後來我又決定這麼寫:

blob.png

這可如何是好??

其實 Kotlin 肯定是有辦法解決這個問題噠!比如上面的場景,我們這麼寫就可以咯:

blob.png

lazy 是 Kotlin 的屬性代理的一個實例,它提供了延遲加載的機制。換句話說,這裏的 lazy 提供了初始化 aTextView 的方法,不過真正初始化這個動作發生的時機卻是在 aTextView 第一次被使用時了。lazy 默認是線程安全的,你當然也可以關掉這個配置,只需要加個參數即可:

blob.png

好,這時候肯定有人要扔西紅柿過來了(再扔點兒雞蛋唄),你這 lazy 只能初始化 val 啊,萬一我要定義一個 var 成員,又需要延遲初始化,關鍵還不爲 null,怎麼辦??

blob.png

lateinit 的使用還是有很多限制的,比如只能在不可 null 的對象上使用,比須爲var,不能爲 primitives(Int、Float之類)等等,不過這樣逼迫你一定要初始化這個變量的做法,確實能減少我們在開發中的遺漏,從而提高開發效率。

至於 lazy 技術,實際上是 Delegate Properties 的一個應用,也就是屬性代理了。在 Kotlin 當中,聲明成員屬性,除了直接賦值,還可以用 Delegate 的方式來聲明,這個 Delegate 需要根據成員的類型(val 或者 var)來提供相應的 getValue 和 setValue 方法,比如一個可讀寫的 Delegate,需要提供下面的方法:

blob.png

好嘴皮不如來個栗子,下面我們就看一個自定義 Delegate,用來訪問 SharedPreference:

blob.png

需要說明的是,這段代碼是我從《Kotlin for Android Developer》的示例中摘出來的。有了這個 Delegate 類,我們就可以完全不需要關心 SharedPreference了,下面給出使用的示例代碼:

blob.png

於是我們再也不需要重複寫那些 getSharedPreference,也不用 edit、commit,再見那些 edit 之後忘了 commit 的日子。有沒有覺得非常贊!

3.3 擴展類

擴展類,就是在現有類的基礎上,添加一些屬性或者方法,當然擴展的這些成員需要導入當前擴展成員所在的包纔可以訪問到。下面給出一個例子:

blob.png

我們已經介紹過 data class,Coordinate 有兩個成員分別是 x 和 y,我們知道通常表示一個二維平面,有這倆夠了;然而我們在圖形學當中經常會需要求得其極座標,所以我們擴展了 Coordinate,增加了一個屬性 theta 表示角度(反正切的值域爲 -π/2 ~ π/2,所以這個式子不適用於二三象限,不過這不是重點了),增加了一個 R 方法來獲得點的半徑,於是我們在 main 方法中就可以這麼用:

blob.png

那麼這個擴展有什麼限制呢?

  • 在擴展成員當中,只能訪問被擴展類在當前作用域內可見的成員,本例中的x 和 y 都是 public 的(Kotlin 默認 public,這個我們後面會提到),所以可以在擴展方法和屬性中直接訪問。

  • 擴展成員與被擴展類的內部成員名稱相同時,擴展成員將無法被訪問到

好的,基本知識就是這些了,下面我們再給出一個實際的例子。

通常我們在 Java 中會自定義一些 LogUtils 類來打日誌,或者直接用 android.util.log 來輸出日誌,不知道大家是什麼感受,我反正每次因爲要輸入 Log.d 還要輸入個 tag 簡直煩的要死,而且有時候恰好這個類還沒有 tag 這個成員,實踐中我們通常會把當前類名作爲 TAG,但每個類都要做這麼個工作,是在是沒有什麼趣味可言(之前我是用 LiveTemplates 幫我的,即便如此也沒有那種流暢的感覺)。

有了 Kotlin 的這個擴展功能,日子就會好過得多了,下面我創建的一個打日誌的方法:

blob.png

有了這個方法,你可以在任何類的方法體中直接寫:

  1. debug(whatever)

然後就會輸出以這個類名爲 TAG 的日誌。

嗯,這裏需要簡單介紹 Kotlin 在泛型中的一個比較重要的增強,這個在 Java 中無論如何也是做不到的:inline、reified。我們再來回頭看一下 debug 這個方法,我們發現它可以通過泛型參數 T 來獲取到T的具體類型,並且拿到它的類名——當然,如果你願意,你甚至可以調用它的構造方法來構造一個對象出來——爲什麼 Kotlin 可以做到呢?因爲這段代碼是 inline 的,最終編譯時是要編譯到調用它的代碼塊中,這時候T的類型實際上是確定的,因而 Kotlin 通過 reified 這個關鍵字告訴編譯器,T 這個參數可不只是個擺設,我要把它當實際類型來用呢。

爲了讓大家印象深刻,我下面給出類似功能的 Java 的代碼實現:

blob.png

而你如果說希望在 Java 中也希望像下面這樣拿到這個泛型參數的類型,是不可以的:

blob.png

就算我們在調用處會寫道 debug < Date >(“blabla”),但這個 Date 在編譯之後還是會被擦除。

3.4 函數式支持(Lambdas)

Java 8 已經開始可以支持 Lambda 表達式了,這種東西對於 Java 這樣一個『根紅苗正』的面向對象編程語言來說還真是顯得不自然,不過對於 Kotlin 來說,就沒那麼多顧忌了。

通常我們需要執行一段異步的代碼,我們會構造一個 Runnable 對象,然後交給 executor,比如這段 java 代碼:

blob.png

用 Kotlin 怎麼寫呢?

  1. executor.submit({    //todo})

一下子省了很多代碼。

那麼實際當中我們可能更常見到下面的例子,這是一段很常見的 Java 代碼,在  Android 的 UI 初始化會見到:

blob.png

那麼我們用 Kotlin 怎麼寫呢?

  1. textView.setOnClickListener{ /*todo*/ }
  2. handler.post{ /*todo*/ }

在 Anko 這個 Android 庫的幫助下,我們甚至可以繼續簡化 OnClickListener 的設置方式:

  1. textView.onClick{ /*todo*/ }

當然,好玩的不止這些,如果結合上一節我們提到的擴展方法,我們就很容易看到 Kotlin 的標準庫提供的類似 with 和 apply 這樣的方法是怎麼工作的了:

blob.png

我們通常會在某個方法體內創建一個對象並返回它,可我們除了調用它的構造方法之外還需要做一些其他的操作,於是就要創建一個局部變量。。。有了 apply 這個擴展方法,我們就可以這麼寫:

blob.png

這樣返回的 StringBuilder 對象實際上是包 "whatever" 這個字符串的。

至於說 Kotlin 對於 RxJava 的友好性,使得我突然有點兒相信緣分這種東西了:

blob.png

3.5 Pattern Matching

記得之前在瀏覽 Scala 的特性時,看到:

  1. object HelloScala{
  2.   // do something
  3. }

覺得很新鮮,這時候有個朋友不屑的說了句,Scala 的模式匹配才真正犀利——Kotlin 當中也有這樣的特性,我們下面就來看個例子:

blob.png

咋一看感覺 when 表達式就是一個增強版的 switch——Java 7 以前的 switch 實際上支持的類型非常有限,Java 7 當中增加的對 String 的支持也是基於 int 類型的——我們可以看到 when 不再像 switch 那樣只匹配一個數值,它的子式可以是各種返回 Boolean 的表達式。

when 表達式還有一種寫法更革命:

blob.png

只要是返回 Boolean 的表達式就可以作爲 when 的子式,這樣 when 表達式的靈活性可見一斑。當然,與 Scala 相比,Kotlin 還是要保守一些的,下面給出一個 Scala 類似的例子,大家感受一下,這實際上也可以體現出 Kotlin 在增加 Java 的同時也儘量保持簡單的設計哲學(大家都知道,畢竟 Scala 需要智商o(╯□╰)o)。

blob.png

運行結果如下:

  1. a tuple with : 1 , 3
  2. [I@2d554825
  3. 3.0, 4.0

3.6 如果你是一個SDK開發者

我曾經做過一段時間的 SDK 開發,SDK 的內部有很多類其實是需要互相有訪問權限的,但一旦類及其成員是 public 的,那麼調用方也就可以看到它們了;而 protected 或者 default 這樣的可見性對於子包卻是不可見的。

用了這麼久 Java,這簡直是我唯一強烈感到不滿的地方了,甚至於我突然明白了 C++ 的 friend 是多麼的有用。

Kotlin 雖然沒有提供對於子包可見的修飾符,不過它提供了i nternal:即模塊內可見。換句話說,internal 在模塊內相當於 public,而對於模塊外就是 private 了——於是乎我們如果開發 SDK,那麼可以減少 api 層的編寫,那些用戶不可見的部分直接用 internal 豈不更好。當然有人會說我們應當有 proguard 做混淆,我想說的是,proguard 自然是要用到的,不過那是 SDK 這個產品加工的下一個環節了,我們爲什麼不能在代碼級別把這個事情做好呢?

關於Kotlin的默認可見性究竟是哪個還有人做出過討論,有興趣的可以參考這裏:Kotlin’s default visibility should be internal (https://discuss.kotlinlang.org/t/kotlins-default-visibility-should-be-internal/1400)。

3.7 DSL

其實我們對 DSL 肯定不會陌生,gradle 的腳本就是基於 groovy 的 DSL,而 Kotlin 的函數特性顯然也是可以支持 DSL 的。比如,我們最終要生成下面的 xml 數據:

blob.png

我們可以構建下面的類:

blob.png

blob.png

我們看到在 main 方法當中,我們用 Kotlin 定義的 dsl 寫出了一個 Project 對象,它有這與 xml 描述的一致的結構和含義,如果你願意,可以構造相應的方法來輸出這樣的 xml,運行之後的結果:

blob.png

當然,這個例子做的足夠的簡陋,如果你有興趣也可以抽象出 "Element",併爲之添加 "Attributes",實際上這也不是很難。

3.7 Kotlin與Android的另一些有趣的東西

寫了很多代碼,卻發現它們幹不了多少事情,終究還是會苦惱的。比如我一直比較痛苦的一件事兒就是:

  1. Button button = (Button) findViewById(R.id.btn);

如果我需要很多個按鈕和圖片,那麼我們要寫一大片這樣的 findViewById。。媽呀。。。這活我幹不了啦。。

不過用 Kotlin 的 Android 擴展插件,我們就可以這樣:

先上佈局文件:

main.xml

blob.png

在 Activity 中:

blob.png

注意到:

  1. import kotlinx.android.synthetic.main.load_activity.*

導入這一句之後,我們就可以直接在代碼中使用 start、textView,他們分別對應於 main.xml 中的 id 爲 start 的按鈕和 id 爲 textView 的 TextView。

於是你就發現你再也不用 findViewById 了,多麼愉快的一件事!!!當然,你還會發現 Toast 的調用也變得簡單了,那其實就是一個擴展方法 toast();而 startActivity 呢,其實就是一個 inline加reified 的應用——這我們前面都提到過了。

還有一個噁心的東西就是 UI 線程和非 UI 線程的切換問題。也許你會用 handler 不斷的 post,不過說真的,用 handler 的時候難道你不顫抖麼,那可是一個很容易內存泄露的魔鬼呀~哈哈,好吧其實我不是說這個,主要是用 handler 寫出來的代碼 實在 太 醜 了 !!

原來在 Java 當中,我們這麼寫:

blob.png

而在 Kotlin 當中呢,我們只需要這麼寫:

blob.png

自己感受一下吧。

下面我們再來提一個有意思的東西,我們從做 Android 開發一開始就要編寫 xml,印象中這個對於我來說真的是一件痛苦的事情,因爲它的工作機制並不如代碼那樣直接(以至於我現在很多時候居然喜歡用 Java 代碼直接寫佈局)——當然,最主要的問題並不是這個,而是解析 xml 需要耗費 CPU。Kotlin 有辦法可以解決這個問題,那就是 DSL 了。下面給出一個例子:

blob.png

一個 LinearLayou t包含了一個 Button,這段代碼你可以直接寫到你的代碼中靈活複用,就像這樣:

blob.png

這樣做的好處真是不少:

  • 比起 xml 的繁瑣來,這真是要清爽很多

  • 佈局本身也是代碼,可以靈活複用

  • 再也不用 findViewById 了,難道你不覺得在這個上面浪費的生命已經足夠多嗎

  • 事件監聽很方便的嵌到佈局當中

  • DSL 方式的佈局沒有運行時的解析的負擔,你的邏輯代碼怎麼運行它就怎麼運行

  • Anko還增加了更多好玩的特性,有興趣的可以參考:Anko@Github (https://github.com/Kotlin/anko)

3.8 方法數之痛

我曾經嘗試用 Scala 寫了個 Android 的 HelloWorld,一切都配置好以後,僅僅引入了 Scala 常見的幾個庫,加上 support-v4 以及 appcompat 這樣常見的庫,結果還是報錯了。是的,65K。。。而且用 Scala 開發 Android 的話,基於 gradle 的構建會讓整個 app 的 build 過程異常漫長,有時候你會覺得自己悟出了廣義相對論的奧義,哦不,你一定是暈了,時間並沒有變慢。

相比之下,Kotlin 的標準庫只有 7000 個方法,比 support-v4 還要小,這正反映了 Kotlin 的設計理念:100% interoperable with Java。其實我們之前就提到,Java 有的 Kotlin 就直接拿來用,而 Scala 的標準庫要有 5W 多個方法,想想就還是想想算了。

4、小結

目前 Kotlin 1.0 已經 release,儘管像 0xffffffff 識別成 Long 類型這樣的 bug 仍然沒有解詳情 (https://youtrack.jetbrains.com/oauth?state=%2Fissue%2FKT-4749):

  1. val int: Int = 0xffffffff // error
  2. val anotherInt: Int = 0xffffffff.toInt() // correct

不過,Kotlin 的教學資源和社區建設也已經相對成熟,按照官方的說法,Kotlin可以作爲生產工具投入開發,詳情可以參考:Kotlin 1.0 Released: Pragmatic Language for JVM and Android (http://blog.jetbrains.com/kotlin/2016/02/kotlin-1-0-released-pragmatic-language-for-jvm-and-android/)。

敢於吃螃蟹,多少有些浪漫主義色彩,我們這些程序員多少可以有些浪漫主義特質,不過在生成環境中,穩定高於一切仍然是不二法則。追求新技術,一方面會給團隊帶來開發和維護上的學習成本,另一方面也要承擔未來某些情況下因爲對新技術不熟悉而產生未知問題的風險——老闆們最怕風險了~~

基於這一點,毫無疑問,Kotlin 可以作爲小工具、測試用例等的開發工具,這是考慮到這些代碼通常體量較小,維護人數較少較集中,對項目整體的影響也較小;而對於核心代碼,則視情況而定吧。

就我個人而言,長期下去,Kotlin 很大可能會成爲我的主要語言,短期內則仍然採用溫和的改革方式慢慢將Kotlin 滲透進來。

發佈了1002 篇原創文章 · 獲贊 2000 · 訪問量 414萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章