寫了許久 Java,有沒有發現其實你寫了太多冗餘的代碼?html
後來你體驗了一下 Python,有沒有以爲不寫分號的感受真是超級爽?java
你雖然勤勤懇懇,可到頭來卻被 NullPointerException 折磨的死去活來,難道就沒有受夠這種日子麼?android
直到有一天你發現本身已經寫了好幾十萬行代碼,發現竟然全是 getter 和 setter!git
哈哈,實際上你徹底能夠不用這麼痛苦,用 Kotlin 替代 Java 開發你的程序,不管是 Android 仍是 Server,你都能像以前寫 Java 同樣思考,同時又能享受到新一代編程語言的特性,說到這裏你是否是開始心動了呢?下面我就經過這篇文章來給你們介紹一下 Kotlin 到底是何方神聖。程序員
話說,Kotlin 是 JetBrain 公司搞出來的,運行在 JVM 上的一門靜態類型語言,它是用波羅的海的一個小島的名字命名的。從外觀上,乍一看還覺得是 Scala,我曾經琢磨着把 Scala 做爲個人下一門語言,不過想一想用 Scala 來幹嗎呢,我又不作大數據,而它又太複雜了o(╯□╰)ogithub
用Kotlin建立一個數據類編程
最初是在 intelliJ 的源碼中看到 Kotlin 的,那時候 Kotlin 的版本還不太穩定,因此源碼老是編譯不過,真是要抓狂啊,還罵『什麼破玩意兒!爲何又出來新語言了?Groovy 還沒怎麼學會,又來個 Kotlin!』話說,Kotlin,難道是『靠它靈』的意思??api
其實通過一年多的發展,Kotlin 1.0已經 release,feature 基本完善,api 也趨於穩定,這時候嘗試也不會有那種被坑的感受了。過年期間也算悠閒,因而用 Kotlin 作了個 app,簡單來講,就是幾個感受:安全
思路與寫 Java 時同樣,不過更簡潔清爽bash
少了冗餘代碼的煩惱,更容易專一於功能的開發,整個過程輕鬆愉快
擴展功能使得代碼寫起來更有趣
空安全和不可變類型使得開發中對變量的定義和初始化傾注了更多關注
啊啊,我不再用寫那個 findViewById 了,真的爽爆有木有!
Kotlin 開發固然使用 JetBrain 系列的 IDE,實際上 intelliJ idea 15 發佈時就已經內置了 Kotlin 插件,更早的版本則須要到插件倉庫中下載安裝 Kotlin 插件——在安裝時你還會看到有個 Kotlin Extensions for Android,不要管他,已通過時了。安裝好之後,咱們就可使用 Kotlin 進行開發了。
接下來咱們用 Android Studio 建立一個 Android 工程,好比叫作 HelloKotlin,在 app 目錄下面的 build.gradle 文件中添加下面的配置:
這裏添加了 Kotlin 對 Android 的擴展,同時也添加了 Kotlin 的 Gradle 插件。
接下來就能夠編寫 Kotlin 代碼了——等等,Android Studio 會幫咱們生成一個MainActivity,你能夠直接在菜單
Code -> Convert Java file to Kotlin file
將這個 Java 代碼轉換爲 Kotlin 代碼。截止到如今,你什麼都不用作,程序就已經能夠跑起來了。
咱們都知道 Jvm 上面的語言,像什麼 Java、Groovy、Jython 啥的,都是要編成虛擬機的字節碼的,一旦編成字節碼,在必定程度上你們就都平等了。
英雄不問出身啊
有人作過一個很是形象的比喻:Java 虛擬機語言就是打羣架。Kotlin 正是充分利用了這一點,它本身的標準庫只是基於 Java 的語言框架作了許多擴展,你在Kotlin 當中使用的集合框架仍然跟你在Java當中同樣。
舉個例子,若是你想要在 Kotlin 中使用 ArrayList,很簡單,Java 的 ArrayList 你能夠隨意使用,這個感受跟使用 Java 沒有任何區別,請看:
固然,Kotlin 標準庫也對這些作了擴展,咱們在享用 Java 世界的一切資源的同時,還能比原生 Java 代碼更滋潤,真是爽爆有木有:
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類
下面是Kotlin代碼
因此咱們在 Android 開發時,就能夠這樣:
view.background = ... textView.text = ...
反過來在 Java 中調用 Kotlin 也毫無壓力,官方文檔C alling Kotlin from Java 對於常見的狀況做了比較詳細的闡述,這裏就再也不贅述。
最初學 Java 的時候,學到一個概念叫 JavaBean,當時就要被這個概念給折磨死了。明明很簡單的一個東西,結果搞得很複雜的樣子,並且因爲當時對於這些數據類的設計概念不是很清晰,於是也並不懂得去覆寫諸如 equals 和 hashcode 這樣重要的方法,一旦用到 HashMap 這樣的集合框架,老是出了問題都不知道找誰。
Kotlin 提供了一種很是簡單的方式來建立這樣的數據類,例如:
data class Coordinate(val x: Double, val y: Double)
僅僅一行代碼,Kotlin 就會建立出一個完整的數據類,並自動生成相應的 equals、hashcode、toString 方法。是否是早就受夠了 getter和setter?反正我是受夠了。
第一次見到空類型安全的設計是在 Swift 當中,那時候還以爲這個東西有點兒意思哈,一旦要求變量不能爲空之後,因它而致使的空指針異常的可能性就直接沒有了。想一想每次 QA 提的 bug 吧,說少了都得有三分之一是空指針吧。
Kotlin 的空安全設計,主要是在類型後面加?表示可空,不然就不能爲 null。
val anInt: Int = null // 錯誤
val anotherInt: Int? = null // 正確
使用時,則:
而對於 Java 代碼,好比咱們在覆寫 Activity 的 onCreate 方法時,有個參數 savedInstanceState:
override fun onCreate(savedInstanceState: Bundle!)
這表示編譯器再也不強制 savedInstanceState 是否可 null,開發者在覆寫時能夠本身決定是否可 null。固然,對於本例,onCreate 的參數是可能爲 null 的,所以覆寫之後的方法應爲:
override fun onCreate(savedInstanceState: Bundle?)
一般來說,教科書式的講法,到這裏就該結束了。然而直到我真正用 Kotlin 開始寫代碼時,發現,有些需求實現起來真的有些奇怪。
仍是舉個例子,我須要在 Activity 當中建立一個 View 的引用,一般咱們在 Java 代碼中這麼寫:
在 Kotlin 當中呢?
每次用 aTextView 都要加倆!,否則編譯器不能肯定它到底是不是 null,因而不讓你使用。。這尼瑪。。。究竟是爲了方便仍是爲了麻煩??
因此後來我又決定這麼寫:
這可如何是好??
其實 Kotlin 確定是有辦法解決這個問題噠!好比上面的場景,咱們這麼寫就能夠咯:
lazy 是 Kotlin 的屬性代理的一個實例,它提供了延遲加載的機制。換句話說,這裏的 lazy 提供了初始化 aTextView 的方法,不過真正初始化這個動做發生的時機倒是在 aTextView 第一次被使用時了。lazy 默認是線程安全的,你固然也能夠關掉這個配置,只須要加個參數便可:
好,這時候確定有人要扔西紅柿過來了(再扔點兒雞蛋唄),你這 lazy 只能初始化 val 啊,萬一我要定義一個 var 成員,又須要延遲初始化,關鍵還不爲 null,怎麼辦??
lateinit 的使用仍是有不少限制的,好比只能在不可 null 的對象上使用,比須爲var,不能爲 primitives(Int、Float之類)等等,不過這樣逼迫你必定要初始化這個變量的作法,確實能減小咱們在開發中的遺漏,從而提升開發效率。
至於 lazy 技術,其實是 Delegate Properties 的一個應用,也就是屬性代理了。在 Kotlin 當中,聲明成員屬性,除了直接賦值,還能夠用 Delegate 的方式來聲明,這個 Delegate 須要根據成員的類型(val 或者 var)來提供相應的 getValue 和 setValue 方法,好比一個可讀寫的 Delegate,須要提供下面的方法:
好嘴皮不如來個栗子,下面咱們就看一個自定義 Delegate,用來訪問 SharedPreference:
須要說明的是,這段代碼是我從《Kotlin for Android Developer》的示例中摘出來的。有了這個 Delegate 類,咱們就能夠徹底不須要關心 SharedPreference了,下面給出使用的示例代碼:
因而咱們不再須要重複寫那些 getSharedPreference,也不用 edit、commit,再見那些 edit 以後忘了 commit 的日子。有沒有以爲很是贊!
擴展類,就是在現有類的基礎上,添加一些屬性或者方法,固然擴展的這些成員須要導入當前擴展成員所在的包才能夠訪問到。下面給出一個例子:
咱們已經介紹過 data class,Coordinate 有兩個成員分別是 x 和 y,咱們知道一般表示一個二維平面,有這倆夠了;然而咱們在圖形學當中常常會須要求得其極座標,因此咱們擴展了 Coordinate,增長了一個屬性 theta 表示角度(反正切的值域爲 -π/2 ~ π/2,因此這個式子不適用於二三象限,不過這不是重點了),增長了一個 R 方法來得到點的半徑,因而咱們在 main 方法中就能夠這麼用:
那麼這個擴展有什麼限制呢?
在擴展成員當中,只能訪問被擴展類在當前做用域內可見的成員,本例中的x 和 y 都是 public 的(Kotlin 默認 public,這個咱們後面會提到),因此能夠在擴展方法和屬性中直接訪問。
擴展成員與被擴展類的內部成員名稱相同時,擴展成員將沒法被訪問到
好的,基本知識就是這些了,下面咱們再給出一個實際的例子。
一般咱們在 Java 中會自定義一些 LogUtils 類來打日誌,或者直接用 android.util.log 來輸出日誌,不知道你們是什麼感覺,我反正每次由於要輸入 Log.d 還要輸入個 tag 簡直煩的要死,並且有時候剛好這個類尚未 tag 這個成員,實踐中咱們一般會把當前類名做爲 TAG,但每一個類都要作這麼個工做,是在是沒有什麼趣味可言(以前我是用 LiveTemplates 幫個人,即使如此也沒有那種流暢的感受)。
有了 Kotlin 的這個擴展功能,日子就會好過得多了,下面我建立的一個打日誌的方法:
有了這個方法,你能夠在任何類的方法體中直接寫:
debug(whatever)
而後就會輸出以這個類名爲 TAG 的日誌。
嗯,這裏須要簡單介紹 Kotlin 在泛型中的一個比較重要的加強,這個在 Java 中不管如何也是作不到的:inline、reified。咱們再來回頭看一下 debug 這個方法,咱們發現它能夠經過泛型參數 T 來獲取到T的具體類型,而且拿到它的類名——固然,若是你願意,你甚至能夠調用它的構造方法來構造一個對象出來——爲何 Kotlin 能夠作到呢?由於這段代碼是 inline 的,最終編譯時是要編譯到調用它的代碼塊中,這時候T的類型其實是肯定的,於是 Kotlin 經過 reified 這個關鍵字告訴編譯器,T 這個參數可不僅是個擺設,我要把它當實際類型來用呢。
爲了讓你們印象深入,我下面給出相似功能的 Java 的代碼實現:
而你若是說但願在 Java 中也但願像下面這樣拿到這個泛型參數的類型,是不能夠的:
就算咱們在調用處會寫道 debug < Date >(「blabla」),但這個 Date 在編譯以後仍是會被擦除。
Java 8 已經開始能夠支持 Lambda 表達式了,這種東西對於 Java 這樣一個『根紅苗正』的面向對象編程語言來講還真是顯得不天然,不過對於 Kotlin 來講,就沒那麼多顧忌了。
一般咱們須要執行一段異步的代碼,咱們會構造一個 Runnable 對象,而後交給 executor,好比這段 java 代碼:
用 Kotlin 怎麼寫呢?
executor.submit({ //todo})
一會兒省了不少代碼。
那麼實際當中咱們可能更常見到下面的例子,這是一段很常見的 Java 代碼,在 Android 的 UI 初始化會見到:
那麼咱們用 Kotlin 怎麼寫呢?
textView.setOnClickListener{ /*todo*/ } handler.post{ /*todo*/ }
在 Anko 這個 Android 庫的幫助下,咱們甚至能夠繼續簡化 OnClickListener 的設置方式:
textView.onClick{ /*todo*/ }
固然,好玩的不止這些,若是結合上一節咱們提到的擴展方法,咱們就很容易看到 Kotlin 的標準庫提供的相似 with 和 apply 這樣的方法是怎麼工做的了:
咱們一般會在某個方法體內建立一個對象並返回它,可咱們除了調用它的構造方法以外還須要作一些其餘的操做,因而就要建立一個局部變量。。。有了 apply 這個擴展方法,咱們就能夠這麼寫:
這樣返回的 StringBuilder 對象其實是包 "whatever" 這個字符串的。
至於說 Kotlin 對於 RxJava 的友好性,使得我忽然有點兒相信緣分這種東西了:
3.5 Pattern Matching
記得以前在瀏覽 Scala 的特性時,看到:
object HelloScala{ // do something
}
以爲很新鮮,這時候有個朋友不屑的說了句,Scala 的模式匹配才真正犀利——Kotlin 當中也有這樣的特性,咱們下面就來看個例子:
咋一看感受 when 表達式就是一個加強版的 switch——Java 7 之前的 switch 實際上支持的類型很是有限,Java 7 當中增長的對 String 的支持也是基於 int 類型的——咱們能夠看到 when 再也不像 switch 那樣只匹配一個數值,它的子式能夠是各類返回 Boolean 的表達式。
when 表達式還有一種寫法更革命:
只要是返回 Boolean 的表達式就能夠做爲 when 的子式,這樣 when 表達式的靈活性可見一斑。固然,與 Scala 相比,Kotlin 仍是要保守一些的,下面給出一個 Scala 相似的例子,你們感覺一下,這實際上也能夠體現出 Kotlin 在增長 Java 的同時也儘可能保持簡單的設計哲學(你們都知道,畢竟 Scala 須要智商o(╯□╰)o)。
運行結果以下:
a tuple with : 1 , 3
[I@2d554825
3.0, 4.0
我曾經作過一段時間的 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)。
其實咱們對 DSL 確定不會陌生,gradle 的腳本就是基於 groovy 的 DSL,而 Kotlin 的函數特性顯然也是能夠支持 DSL 的。好比,咱們最終要生成下面的 xml 數據:
咱們能夠構建下面的類:
咱們看到在 main 方法當中,咱們用 Kotlin 定義的 dsl 寫出了一個 Project 對象,它有這與 xml 描述的一致的結構和含義,若是你願意,能夠構造相應的方法來輸出這樣的 xml,運行以後的結果:
固然,這個例子作的足夠的簡陋,若是你有興趣也能夠抽象出 "Element",併爲之添加 "Attributes",實際上這也不是很難。
寫了不少代碼,卻發現它們幹不了多少事情,終究仍是會苦惱的。好比我一直比較痛苦的一件事兒就是:
Button button = (Button) findViewById(R.id.btn);
若是我須要不少個按鈕和圖片,那麼咱們要寫一大片這樣的 findViewById。。媽呀。。。這活我幹不了啦。。
不過用 Kotlin 的 Android 擴展插件,咱們就能夠這樣:
先上佈局文件:
main.xml
在 Activity 中:
注意到:
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 當中,咱們這麼寫:
而在 Kotlin 當中呢,咱們只須要這麼寫:
本身感覺一下吧。
下面咱們再來提一個有意思的東西,咱們從作 Android 開發一開始就要編寫 xml,印象中這個對於我來講真的是一件痛苦的事情,由於它的工做機制並不如代碼那樣直接(以致於我如今不少時候竟然喜歡用 Java 代碼直接寫佈局)——固然,最主要的問題並非這個,而是解析 xml 須要耗費 CPU。Kotlin 有辦法能夠解決這個問題,那就是 DSL 了。下面給出一個例子:
一個 LinearLayou t包含了一個 Button,這段代碼你能夠直接寫到你的代碼中靈活複用,就像這樣:
這樣作的好處真是很多:
比起 xml 的繁瑣來,這真是要清爽不少
佈局自己也是代碼,能夠靈活複用
不再用 findViewById 了,難道你不以爲在這個上面浪費的生命已經足夠多嗎
事件監聽很方便的嵌到佈局當中
DSL 方式的佈局沒有運行時的解析的負擔,你的邏輯代碼怎麼運行它就怎麼運行
Anko還增長了更多好玩的特性,有興趣的能夠參考:Anko@Github (https://github.com/Kotlin/anko)
我曾經嘗試用 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 多個方法,想一想就仍是想一想算了。
目前 Kotlin 1.0 已經 release,儘管像 0xffffffff 識別成 Long 類型這樣的 bug 仍然沒有解詳情 (https://youtrack.jetbrains.com/oauth?state=%2Fissue%2FKT-4749):
val int: Int = 0xffffffff // error
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 滲透進來。
一句話,Kotlin 是用來提高效率的,若是在你的場景中它作不到,甚至成了拖累,請放開它。
若是您以爲咱們的內容還不錯,就轉發到朋友圈,和小夥伴一塊兒分享吧~
原文:http://mp.weixin.qq.com/s?__biz=MzA3NTYzODYzMg==&mid=404087761&idx=1&sn=d80625ee52f860a7a2ed4c238d2151b6