本文首發於 vivo互聯網技術 微信公衆號
連接:https://mp.weixin.qq.com/s/UV23Uw_969oVhiOdo4ZKAw
做者:連凌能java
Kotlin,已經被Android官方宣佈 kotlin first 的存在,去翻 Android 官方文檔的時候,發現提供的示例代碼已經變成了 Kotlin。Kotlin的務實做風,提供了不少特性幫助開發者減小冗餘代碼的編寫,能夠提升效率,也能減小異常。數據庫
本文簡單談下Kotlin中的函數,包括表達式函數體,命名參數,默認參數,頂層函數,擴展函數,局部函數,Lambda表達式,成員引用,with/apply函數等。從例子入手,從通常寫法到使用特性進行簡化,再到原理解析。微信
經過下面這個簡單的例子看下函數聲明相關的概念,函數聲明的關鍵字是fun,嗯,比JS的function還簡單。app
Kotlin中參數類型是放在變量:後面,函數返回類型也是。jvm
固然, Kotlin是有類型推導功能,若是能夠根據函數表達式推導出類型,也能夠不寫返回類型。ide
可是上面的仍是有點繁瑣,還能再簡單,在 Kotlin中if是表達式,也就是有返回值的,所以能夠直接return,另外判斷式中只有一行一句也能夠省略掉大括號:函數
還能在簡單點嗎?能夠,if是表達式,那麼就能夠經過表達式函數體返回:佈局
最終只須要一行代碼。post
Exampleui
再看下面這個例子,後面會基於這個例子進行修改。這個函數把集合以某種格式輸出,而不是默認的toString()。
<T>是泛型,在這裏形參集合中的元素都是T類型。返回String類型。fun <T> joinToString(
先來看下函數調用,相比Java, Kotlin中能夠相似於JavaScript中帶命名參數進行調用,並且能夠不用按函數聲明中的順序進行調用,能夠打亂順序,好比下面:
Java裏面有重載這一說,或者JavaScript有默認參數值這一說,Kotlin採用了默認參數值。調用的時候就不須要給有默認參數值的形參傳實參。上面的函數改爲以下:
那麼調用的時候若是默認參數值本身的知足要求,就能夠只傳入集合list便可。
不一樣於Java中函數只能定義在每一個類裏面,Kotlin採用了JavaScript 中的作法,能夠在文件任意位置處定義函數,這種函數稱爲頂層函數。
編譯後頂層函數會成爲文件類下的靜態函數,好比在文件名是join.kt下定義的joinToString函數能夠經過JoinKt.joinToSting調用,其中JoinKt是編譯後的類名。
看下上面函數編譯後的效果:// 編譯成class文件後反編譯結果
接下來看下Kotlin中很重要的一個特性,擴展函數。
擴展函數是類的一個成員函數,不過定義在類的外面
擴展函數不能訪問私有的或者受保護的成員
擴展函數也是編譯成靜態函數
因此能夠在Java庫的基礎上經過擴展函數進行封裝,僞裝好像都是在調用Kotlin本身的庫同樣,在Kotlin中Collection就是這麼幹的。
再對上面的joinToString來一個改造,終結版:
在這裏聲明成了Collection接口類的擴展函數,這樣就能夠直接經過list進行調用, 在擴展函數裏面照常可使用this,這裏的this就是指向接收者對象,在這裏就是list。
常常咱們須要對代碼進行重構,其中一個重要的措施就是減小重複代碼,在Java中能夠抽取出獨立的函數,但這樣有時候對總體結構並不太好,Kotlin提供了局部函數來解決這個問題。
顧名思義,局部函數就是能夠在函數內部定義函數。先看下沒有使用局部函數的一個例子,這個例子先對傳進來的用戶名和地址進行校驗,只有都不爲空的狀況下才存進數據庫:
上面有重複的代碼,就是對name和address的校驗重複了,只是入參的不一樣,所以能夠抽出一個校驗函數,使用局部函數重寫:
佈局函數能夠訪問所在函數中的全部參數和變量。
若是不支持Lambda都很差意思稱本身是一門現代語言,來看看Kotlin中的表演。
Lambda本質上是能夠傳遞給其餘函數的一小段代碼,能夠當成值處處傳遞
Lambda表達式以左大括號開始,以右大括號結束,箭頭->分割成兩邊,左邊是入參,右邊是函數體。
若是Lambda表達式是函數調用的最後一個實參,能夠放到括號外邊;
當Lambda是函數惟一實參時,能夠去掉調用代碼中的空括號;
和局部變量同樣,若是Lambda參數的類型能夠被推導出來,就不須要顯示的指定。
若是在函數內部使用Lambda,能夠訪問這個函數的參數,還有在Lambda以前定義的局部變量。
考慮這麼一種狀況,若是一個函數A接收一個函數類型參數,可是這個參數功能已經在其它地方定義成函數B了,有一種辦法就是傳入一個Lambda表達式給A,在這個表達式中調用B,可是這樣就有點繁瑣了,有沒有能夠直接拿到B的方式呢?
我都說了這麼多了,確定是有了。。。那就是成員引用。
若是Lambda恰好是函數或者屬性的委託,能夠用成員引用替換。
Ps:無論引用的是函數仍是屬性,都不要在成員引用的名稱後面加括號
引用頂層函數
若是Lambda要委託給一個接收多個參數的函數,提供成員引用代替會很是方便:fun sendEmail(person: Person, message: String) {
能夠用 構造方法引用 存儲或者延期執行建立類實例的動做,構造方法的引用的形式是在雙冒號後指定類名稱:
還能夠用一樣的方式引用擴展函數。
接下來稍微探究下Lambda的原理。
自Kotlin 1.0起,每一個Lambda表達式都會被編譯成一個匿名類,除非它是一個內聯Lambda。後續版本計劃支持生成Java 8字節碼,一旦實現,編譯器就能夠避免爲每個lambda表達式都生成一個獨立的.class文件。
若是Lambda捕捉了變量,每一個被捕捉的變量會在匿名類中有對應的字段,並且每次調用都會建立一個這個匿名類的新實例。不然,一個單例就會被建立。類的名稱由Lambda聲明所在的函數名稱加上後綴衍生出來,這個例子中就是TestLambdaKt$main$1.class。
編譯後,生成兩個文件。
先看下TestLambdaKt$main$1.class, 構造一個靜態實例ch05.TestLambdaKt$main$1 INSTANCE,在類加載的時候進行賦值,同時繼承接口Function0,實現invoke方法:
再看下另一個類TestLambdaKt.class, 在main方法中傳入TestLambdaKt$main$1.INSTANCE給方法salute,在方法salute中調用接口方法invoke,見上面。
Ps:Lambda內部沒有匿名對象那樣的的this:沒有辦法引用到Lambda轉換成的匿名類實例。從編譯器角度看,Lambda是一個代碼塊不是一個對象,不能把它當成對象引用。Lambda中的this引用指向的是包圍它的類。
若是在Lambda中要用到常規意義上this呢?這個就須要帶接收者的函數。看下比較經常使用的兩個函數with和apply。
直接上Kotlin的源碼,with在這裏聲明成內聯函數(後面找機會說), 接收兩個參數,在函數體裏面對接收者調用Lambda表達式。在Lambda表達式裏面能夠經過this引用到這個receiver對象。
看個例子:
with改造, 在with裏面就不用顯示經過StringBuilder進行append調用。
with返回的值是執行Lambda代碼的結果,該結果是Lambda中的最後一個表達式的值。若是想返回的是接收者對象,而不是執行Lambda的結果,須要用apply函數。
apply函數幾乎和with函數如出一轍,惟一的區別就是apply始終返回做爲實參傳遞給它的對象,也就是接收者對象。
apply被聲明稱一個擴展函數,它的接收者變成了做爲實參傳入的Lambda的接收者。
能夠調用庫函數再簡化:
本文只是說了Kotlin中關於函數的一點特性,固然也沒講全,好比內聯函數,高階函數等,由於再寫下去太長了,因此後面再補充。從上面幾個例子也能大概感覺到Kotlin的務實做風,提供了不少特性幫助開發者減小冗餘代碼的編寫,能夠提升效率,也能減小異常,讓程序猿早點下班,永葆頭髮烏黑靚麗。
更多內容敬請關注 vivo 互聯網技術 微信公衆號
注:轉載文章請先與微信號:labs2020 聯繫。