你們好,很久不見。從Kotlin發佈到如今已經有快十個年頭了,從2016年發佈正式版發展到如今已經有愈來愈多的開發者開始使用Kotlin開發項目,特別是安卓開發者,由於谷歌在2017年的 I/O 大會上正式宣佈Kotlin正式成爲安卓的一級開發語言,在2019年的 I/O大會上又宣佈Kotlin爲安卓的第一開發語言。。java
我從2018年下半年開始學習的Kotlin,最開始買了一本《Kotlin從零到精通》,跟着學習了一遍,把Kotlin的基本語法學習了一遍,因爲待的公司比較小,安卓開發者並很少,並且當時正好有一個新的項目,因此我提議整個項目採用Kotlin進行開發,其實當時很冒險,由於那個項目挺着急,三個安卓開發都不咋會Kotin,因此在項目開發期間一直是邊學邊寫,項目完成以後感受本身對Kotlin已經理解的差很少了(其實只是會用而已,項目中有的地方仍是使用Java)。安全
後來看一些開發者論壇看你們學習Kotlin的愈來愈多,提到的好多東西居然都看不懂,最過度的是已經使用kotlin寫了一個項目了,Kotlin中的一些關鍵字都不知道是幹啥用的,實在慚愧。app
以前一直關注郭神的博客,並且安卓入門也看的是郭神的《第二行代碼》。後來據說要出《第三行代碼》,並且裏面有Kotlin的詳細講解,因此書第一天發售的時候就立馬搶了一本簽名版,還沒來的及好好看看,公司就讓我出差了,一直到如今纔有空看看,看了看感受以前寫的代碼好多都不太好,好多Kotlin中好用的東西都沒使用到,我使用的Kotlin只是把Java代碼轉成了Kotlin,沒有一點意義。好了,下面開始知識點了,敲黑板劃重點!ide
Kotlin中的標準函數指的是在 Standard.kt 中定義的函數,下面來寫一下我認爲常用的標準函數吧!函數
最開始決定使用Kotlin的時候的一個重要緣由就是它把空指針異常提到了語言層面,可是這也是讓不少像我同樣的開發者頭疼的地方。。工具
是,Kotlin爲了空安全不容許定義爲空的,想要定義的話就必須加上問號,可是。。。。不少狀況就像下面的代碼:性能
爲了Kotlin的空安全必須使用問號點或者兩個感嘆號點來消除空安全的報錯,只是爲了消除報錯,在Java中根本不須要的好嘛!要是隻使用一次兩次還好,我們使用問號或者感嘆號還好,但若是是一堆調用的呢?好比下面:學習
參數多的時候怎麼搞。。不說寫的速度,煩都煩死了。。。。ui
後來。。。。發現根本不必這樣寫啊!能夠這樣啊:this
var zhu:ZhuJ? = null
fun test() {
zhu?.let { zhu->
zhu.name
zhu.phone
zhu.age
zhu.sex
}
}
複製代碼
是否是瞬間感受代碼優雅了好多。。。全局變量都沒問題,方法的實參更沒問題了。。
知識點啊兄弟們,早知道這樣就不寫一堆問號和感嘆號了。。。
這個標準函數的做用是Lambda中的代碼會持有對象的上下文,其最後一行代碼爲返回值。
這麼說不太好理解,來一段代碼你們先看下吧:
operator fun String.times(n: Int): String {
val sb = StringBuilder()
repeat(n){
sb.append(this)
}
return sb.toString()
}
複製代碼
這是一個String類的運算符重載的一個方法,意思很簡單,就是重複字符串,參數爲重複幾回。你們能夠發現,在這個方法中的 StringBuilder 對象被使用了好幾次,沒一會都須要寫一次,可是。。。若是使用了with標準函數的話。。。。
operator fun String.times(n: Int): String {
return with(StringBuilder()) {
repeat(n) {
append(this@times)
}
toString()
}
}
複製代碼
是否是感受清爽了些許,不少狀況能夠這樣來調用。
這個標準函數的做用其實和 with 基本一致,只是使用方法上有所不一樣,with 須要括號中寫入對象來進行操做,run 則是對象點進行操做,上面代碼使用 run 改寫以後的代碼以下:
operator fun String.times(n: Int): String {
return StringBuilder().run {
repeat(n) {
append(this@times)
}
toString()
}
}
複製代碼
這塊要注意了,apply 使用方式和run一致,可是不一樣的是:最後一行不做爲返回值,廢話很少說,還拿上面代碼改寫:
operator fun String.times(n: Int): String {
return StringBuilder().apply {
repeat(n) {
append(this@times)
}
}.toString()
}
複製代碼
with 和 run 的最後一行都是返回值,而apply澤不是,這塊必定要注意。
這塊須要好好的總結下了,真的是,都寫了一個項目了連使用語言的關鍵字都沒認全。。一個一個來!
這個關鍵字其實使用的不少,在定義全局變量爲空的時候並非非得用問號設置爲可空的,若是你能夠肯定必定不爲空可使用 lateinit 這個關鍵字來定義全局變量,舉個栗子:
lateinit var zhuJ: ZhuJ
複製代碼
當這樣定義全局變量的時候就無需設置爲可空了,好比安卓項目中的 adapter ,我們確定能確認會賦值,不會爲空,那麼就可使用 lateinit 了。
這塊須要注意的是,即便我們以爲不會爲空,但確定會有特殊狀況須要進行判斷,須要進行判斷的話要使用 isInitialized ,使用方法以下:
if (::zhuJ.isInitialized){
// 判斷是否已經進行賦值
}
複製代碼
這個關鍵字以前一直沒有進行使用,它用來修飾類,含義爲密封類,以前一直沒搞懂這個密封類有啥說啥用,這兩天好好看了下,我理解的做用就是:可使代碼更加嚴密。
這樣說感受有點抽象,再舉個栗子吧,平時我們在封裝一些工具的時候通常只會有成功和失敗,我們的作法通常是定義一個接口,而後再定義一個成功類和失敗類來實現這個接口,最後再進行判斷:
class Success(val msg: String) : Result
class Fail(val error: Throwable) : Result
fun getResult(result: Result) = when (result) {
is Success -> result.msg
is Fail -> result.error.message
else -> throw IllegalArgumentException()
}
複製代碼
上面代碼都是我們通常寫的,雖然只有兩種狀況,可是必須再寫 else 來進行判斷,若是不寫的話編譯就過不了。但若是使用密封類的話就不會有這種狀況出現:
sealed class Results
class Success(val mag: String) : Results()
class Failure(val error: Exception) : Results()
fun getMessage(result: Results) {
when (result) {
is Success -> {
println(result.mag)
}
is Failure -> {
println(result.error.toString())
}
}
}
複製代碼
不只不用再寫else,並且在進行 when 判斷時,kotlin 會檢查條件是否包含了全部的子類,若是沒有會提示你加上,這樣就大大提升的代碼的魯棒性,也不會出現沒有判斷到的問題。
這個關鍵字是運算符重載,其實在上面標準函數中已經使用到了,就是能夠對運算符進行從新自定義,用來實現一些代碼上不對勁但實際上對勁的需求,使用起來也很舒服。
這裏來講一下我們經常使用的運算符須要重載的函數吧:加號對應 plus、減號對應minus、乘號對應 times、除號對應 div、取餘對應 rem、自增對應 inc、自減對應 dec。
具體使用方法就是上面那樣,再寫下吧:
operator fun String.times(n: Int): String {}
複製代碼
這個關鍵字能夠用來修飾類和方法,它的做用很簡單,就是限制不一樣 module 的訪問,若是在 A module 中定義了一個 internal 方法,那麼這個方法只能在 A module 中進行調用,在 B module 中是沒法訪問的。
這個關鍵字很簡單,用來修飾類,可是。。。只能用來修飾內部類。
我們來寫個栗子你們就知道了:
class Test {
var num: String? = null
class Zhu() {
var nums: String? = null
fun adds() {
nums?.let { it.length }
}
}
inner class Jiang() {
var nums: String? = null
fun adds() {
nums?.let {
it.length
}
}
}
}
複製代碼
上面代碼很簡單,只是定義了一個 Test 類,其中一個是直接在內部建立的 Zhu 類,另外一個是使用 inner 關鍵字修飾的 Jiang 類。我們直接來調用下看下有什麼區別吧:
Test().Jiang().nums
Test.Zhu().nums
複製代碼
你們發現沒有,若是要使用 inner 修飾內部類的話須要先獲取到 Test 類的實例才能夠進行使用,而直接建立的 Zhu 類則不須要。
這個關鍵字的意思是內聯函數,它的用法很是簡單,只須要在高階函數前加上 inline 關鍵字便可。若是對高階函數不太清楚的,建議去看下扔物線的一個視頻,好像是講解 Lanbda 的。
簡單說下吧,高階函數並無想象中的難,只是名字聽着感受很高大上而已,簡單來講就是傳入方法(其實本質上仍是對象)看成方法參數即爲高階函數。高階函數的原理其實就是把方法參數轉爲接口,並建立匿名內部類進行調用,因此每次調用這樣的 Lambda 都會建立一個新的匿名內部類和接口實例,形成額外的開銷。
因此這就是 inline 出現的緣由,它能夠去掉這些開銷,並無什麼特殊的,只是進行替換,就是在你調用的地方把方法參數進行替換,從而減小內存和性能的開銷。來看下使用方法吧:
inline fun high(block:(Int,Int) -> Int,block2:(Int,Int) -> Int){
block.invoke(5,6)
block2.invoke(4,5)
}
複製代碼
誒,這個關鍵字和上面內聯函數的關鍵字好像是吧!這是由於若是一個高階函數中有兩個或以上的方法參數存在的話,若是使用 inline 關鍵字的話會把全部的方法參數都變爲內聯函數,爲何不都替換呢?由於內聯函數的函數類型參數在編譯的時候會進行代碼替換,因此沒有真正的參數類型,但非內聯函數的函數類型參數能夠自由的傳遞給其餘任何函數,而內聯函數類型參數只容許傳遞給另外一個內聯函數。
說了這麼多就是爲了引出 noinline 的存在乎義,使用方法很簡單:
inline fun high(block:(Int,Int) -> Int,noinline block2:(Int,Int) -> Int){
block.invoke(5,6)
block2.invoke(4,5)
}
複製代碼
是否是很簡單,只要在方法參數前加上 noinline 關鍵字便可。
既然已經說了 noinline 關鍵字,那麼也得說下 crossnoinline 了。它的做用是:讓沒法使用內聯函數的方法使用內聯函數。
爲何有沒法使用內聯函數的函數呢?非內聯函數沒法直接 return ,可是內聯函數能夠,因此若是在高階函數中建立或者使用了另外的 Lambda 或匿名類的實現的話即會報錯。再舉個栗子:
上面代碼使用了我們很是熟悉的 Runnable ,可是發現報錯了,爲何呢?上面已經說出了答案,這就是 crossinline 關鍵字的做用,可讓沒法使用內聯函數的函數來使用內聯函數:
完美解決!crossinline 的做用主要是用於保證內聯函數中的 Lambda 表達式中必定不會使用 return 關鍵字,這樣也就沒有衝突了。這樣也有一個壞處,就是咱們也沒法調用 Runnable 中使用 return 進行返回了。
這個關鍵字其實很好用,我們可使用它來一些很騷的操做:
val result = "zhujiang" * 3
val a = result begin "zhu"
複製代碼
是否是沒見過這樣的寫法?複製到你電腦上確定報錯。infix 主要的做用就是定義一些語義上很舒服的寫法,好比上面的 result begin "zhu" 這樣的調用方式:
infix fun String.begin(prefix:String):Boolean = startsWith(prefix)
複製代碼
是否是很好用,是否是已經想到不少騷操做了?哈哈哈
可是!
要注意如下兩點:
- infix 不能定義成頂層函數,必須是某個類的成員函數,可以使用擴展方法的方式將它定義到某個類中
- infix 函數必須且只有能接收一個參數,類型的話沒有限制。
這個關鍵字的意思是委託。來一個使用方法看看吧:
class MySet<T>(val help:HashSet<T>) :Set<T> by help{
override fun isEmpty(): Boolean {
return false
}
}
複製代碼
能夠爲一些類建立委託類並重寫或添加一些本身寫的方法。
泛型你們再熟悉不過了,Java 中我們使用的也很是多,例如 List 、HashMap<String,String>等等。
其實使用和 Java 中差很少,栗子又來了:
class Generic <T>{
fun method(parem:T):T{
return parem
}
}
複製代碼
上面是在類上的使用,固然方法中也能夠進行使用:
fun <S> meth(parem:S):S{
return parem
}
複製代碼
在 Java 中是絕對沒有的,也是不現實的,由於 Java 的泛型擦出機制。。。
可是在Kotlin中是能夠實現的,可是。。。。有條件!
那麼,實化有什麼做用呢?來看代碼吧:
inline fun <reified T> startActivity(context:Context) {
context.startActivity(Intent(context,T::class.java))
}
複製代碼
知道了吧。。很方便的!
這塊。。。提及來有點麻煩,下一篇文章來專門寫寫泛型的逆變和協變吧!先欠着!
先總結到這裏吧,其實 Kotlin 中還有不少好玩的東西須要咱們去探索,好比協程,項目中其實用到了不少,但總感受使用的不夠好,須要有空好好扣一扣。雖說內容並很少,但也寫了很久,文中的知識都是跟着郭神的《第三行代碼》學習的,也推薦你們購買,一本書能學到一個知識點就不虧,支持正版,別爲了省幾十塊錢下載盜版。。。
若是本文對你有幫助,請別忘記三連啊。若是本文有描述不恰當或錯誤的地方,請提出來,感激涕零。就這樣,下回見。