kotlin - 高級特性

kotlin 上手很簡單瓢捉,因為可以完美支持 java 捂刺,和 java 比較像的緣故到踏,我們熟悉下 kotlin 的語法鲫竞,1-2天就能寫出 java 語法式的 kotlin 代碼了,但是我們絕對不能只不如此固阁,kotlin 本身的高級特性代表著語言的發(fā)展趨勢壤躲,本身也是很簡單,高效的备燃,我們必須真正熟悉 kotlin 自身的寫法碉克,不要抗拒,擁抱 kotlin并齐,零碎東西不少漏麦,但是我們總結(jié)一下客税,平時多用用,也就熟悉了

本文包含以下內(nèi)容:


Lazy / lateinit

kotlin 對于 null 有著嚴格的使用限制撕贞,處處可見 更耻?不是,非 null 判斷一直是代碼的痛點捏膨,kotlin 只是把這種 痛點變得對 coder 來說更有好了秧均,但是凡事總有一利一弊不是

kotlin 為了準確定義是不是 null ,要求我們在定義全局變量号涯,時必須顯示的賦值

傳統(tǒng) java 中我們這樣定義全局變量

public class Car {
    
    public String name ;
    
}

kotlin 中我們必須賦值目胡,否則會報錯


這里 kotlin 提供了2個選擇,Lazy / lateinit 链快,都是延遲加載誉己,但是有區(qū)別:

Lazy 只能修飾 val 不可變參數(shù),等同于 java 的 final 域蜗,其次 Lazy 后跟一個 {} 復(fù)制表達式巨双,本質(zhì)是在一個工廠函數(shù),只有在第一次調(diào)用時生效(既首席創(chuàng)建對象時)霉祸,常用于單例模式

val lazyValue: String by lazy {

    // println 是對象創(chuàng)建時初始化操作
    println("computed!")
    
    // Hello 是返回的對象
    "Hello"
}

//調(diào)用兩次
println(lazyValue)
println(lazyValue)
// 第一次
computed!
Hello

// 第二次
Hello

lazy 方法本質(zhì)是個 lamber 表達式筑累,在 lazy() 中我們可以傳入線程類型參數(shù):

lateinit 我們可以在可以修飾任意參數(shù),可以是 var 茵典、 val 的,我們在聲明成員變量時可以不指定具體數(shù)值统阿,但是 lateinit 修飾的參數(shù)必須在合適的地方初始化,否則編譯不會通過

我們看個例子扶平,下面我們聲明 lateinit 的參數(shù),但是不初始化直接使用

open class News(var room: String) {

    lateinit var name: String
    lateinit var book: Book

    fun speak() {
        println(name)
    }

    fun price() {
        println(book)
        print(room)
    }
}

使用

        btn_name.setOnClickListener(View.OnClickListener {
            val news = News("book")
            news.speak()
            news.price()
        })

編譯會報 UninitializedPropertyAccessException 異常结澄,未初始化的參數(shù)不可達


這就是 Lazy 和 lateinit 的區(qū)別岸夯,lateinit 我們在使用前必須初始化才行们妥,而 Lazy 在我們首席使用的時候才會創(chuàng)建對象猜扮,很像 java 中的懶漢式,餓漢式监婶。上面的代碼我們即使對 lateinit 的參數(shù)加了 !null 的判斷也沒用

如果在我們的代碼場景中會有像 java 那樣旅赢,成員變量依靠外接傳遞,那么 kotlin 也提供了相關(guān)寫法惑惶,核心就是拋棄 kotlin 關(guān)于 null 的操作鲜漩,完全還原 java 的環(huán)境,還是上面的代碼

open class News(var room: String) {

    var name: String? = null
    var book: Book? = null

    fun speak() {

        if (name != null) {
            println(name!!)
        }
    }

    fun price() {
        if (book != null && room != null) {
            println(book!!)
            print(room!!)
        }
    }
}

我們就不用 lateinit 來修飾啦集惋,在使用這個參數(shù)時添加 !! 后綴表示按照 java 語法進行孕似,注意我們現(xiàn)在得進行 !null 判斷啦,要不會報錯的哦~


Delegates 屬性觀察者

Delegates 我是愿意稱為屬性觀察者的刮刑,Delegates 下面包含一些列函數(shù)喉祭,這是 kotlin 獨有的特性,允許我們在屬性賦值時添加觀察者雷绢,攔截器操作

我們常用的是 observable / vetoable 這2個函數(shù)

  • observable 可以觀察參數(shù)的變化
  • vetoable 相當于參數(shù)攔截器泛烙,可以攔截不符合條件的復(fù)制操作
  • 但是在這2個函數(shù)內(nèi)我們都不能主動的修改參數(shù)值,代碼檢查會提示我們

我們來看看代碼:

open class News(var room: String) {

    var title: String by Delegates.observable("title_default") { property, oldValue, newValue ->
        Log.d("AAA", "屬性變化:屬性名:$property  舊值:$oldValue  新值:$newValue")
    }

    var price: Int by Delegates.vetoable(100, { property, oldValue, newValue ->
        if (newValue > 100) {
            Log.d("AAA", "屬性變化:屬性名:$property  舊值:$newValue > 100 不符合需求不能更改數(shù)據(jù)")
            return@vetoable false
        }
        return@vetoable true
    })
}

Delegates 下屬函數(shù)會提供給我們3個參數(shù)翘紊,property(參數(shù)名) / oldValue(舊值) / newValue(新值)蔽氨,接收2個參數(shù),前一個是默認值帆疟,賦值時注意數(shù)據(jù)類型鹉究;后一個接受一個對象函數(shù)用來包裹我們的代碼

observable 函數(shù)沒有返回值,vetoable 函數(shù)有返回值踪宠,true 表示允許參數(shù)修改自赔,false 反之不允許,數(shù)據(jù)不會變更柳琢。這里注意我們要顯式的使用 return@vetoable 退出函數(shù)绍妨,否則會出現(xiàn)代碼穿透的問題

下面來試一下,我們給 title 和 price 賦值看看:

val news = News("book")
news.title = "AAA"
news.price = 200
Log.d("AA", "重新對 news 賦值后柬脸,news 的值:${news.price}")
屬性變化:屬性名:property title (Kotlin reflection is not available)  舊值:title_default  新值:AAA
屬性變化:屬性名:property price (Kotlin reflection is not available)  舊值:200 > 100 不符合需求不能更改數(shù)據(jù)
重新對 news 賦值后他去,news 的值:100

map 構(gòu)造函數(shù)委托

kotlin 的這個 map 委托是用與構(gòu)造函數(shù)的,生成數(shù)據(jù)的倒堕,用于 json 解析灾测,我覺得用這個 map 做 json 解析不是好,不如 gson

map:

// map 用在類聲明處涩馆,傳參用
class BookData(map: Map<String, Any>) {

    val name: String by map
    val price: Int by map

    fun speak() {
        Log.d("AAA", "BookData: name = $name , price = $price")
    }
}

// 使用:
var bookData = BookData(mapOf("name" to "", "price" to 88))
bookData.speak()

map 只能修飾 val 不可變參數(shù)允坚,那么相應(yīng)的就有 MutableMap 蛾号,注意 Mutable 可變早 kotlin 已經(jīng)出現(xiàn)在好幾個地方了

MutableMap :

class BookData(map: MutableMap<String, Any>) {

    var name: String by map
    var price: Int by map

    fun speak() {
        Log.d("AAA", "BookData: name = $name , price = $price")
    }
}

MutableMap 可以操作 var 可變參數(shù)了鲜结,和 map 就是這點差距

需要注意的是,使用 map 賦值生成數(shù)據(jù)對象時精刷,比如傳入所有的屬性值,沒有值的也要給埂软,要不會拋出 error勘畔,參數(shù)類型給錯了也會報錯

錯誤賦值丽惶,缺少一個屬性值:

var bookData = BookData(mapOf("name" to "android"))
bookData.speak()

前面說到有人推薦使用 map 來進行 json 操作钾唬,這里我推行各位必須找資料看明白再決定是不是使用 map 這個特性


智能轉(zhuǎn)換類型

kotlin 是弱類型語言,通過 var 大家都了解吧奕巍,這點和 java 不同, java 這種強類型設(shè)計早早就被時代慢慢淘汰了伍绳,到現(xiàn)在 var / val 這種弱類型設(shè)計已經(jīng)是行業(yè)準則了,java 在 jdk 10 時也開始支持 var 了

var 的好處是語言可以自定判斷數(shù)據(jù)類型效床,從而進行無痕式的類型轉(zhuǎn)換,這點對于我們來說體驗是很 nice 的憋沿,代碼少了沪猴,也不會打算思路寫討厭重復(fù)的代碼了采章,邏輯直接一氣呵成悯舟,連貫舒服我婆覺得是語言進行的特點

kotlin 強制類型轉(zhuǎn)換

var kkk:Any = "123"
var mmm:String = kkk as String

類型判斷

var kkk:Any = "123"
var mmm:String = kkk as String
            
mmm is String

智能自動轉(zhuǎn)換類型

var kkk:Any = "123"

// 不用我們自己手動寫轉(zhuǎn)換了吧抵怎,這是因為我們已經(jīng)做了類型判斷了岭参,所以編譯器認為類型安全默認給我們轉(zhuǎn)了
if (kkk is String) {
    kkk.length
}

當然智能轉(zhuǎn)換不是萬能的,機器畢竟不是人不是姿染,適用以下規(guī)則:

  • val 局部變量——總是可以秒际,局部委托屬性除外
  • val 屬性——如果屬性是 private 或 internal程癌,或者該檢查在聲明屬性的同一模塊中執(zhí)行。智能轉(zhuǎn)換不適用于 open 的屬性或者具有自定義 getter 的屬性
  • var 局部變量——如果變量在檢查和使用之間沒有修改进萄、沒有在會修改它的 lambda 中捕獲锐峭、并且不是局部委托屬性
  • var 屬性——決不可能(因為該變量可以隨時被其他代碼修改)

this表達式

Koltin 在作用域這塊有更寬泛的使用,這點在 this 關(guān)鍵字的使用上可以看的很明白援雇,比 java 的 this 使用更靈活椎扬,Kotlin 的 this 關(guān)鍵字可以 + @label 標簽 來指定 this 具體的代表對象

class A { // 隱式標簽 @A
    inner class B { // 隱式標簽 @B
        fun Int.foo() { // 隱式標簽 @foo
            val a = this@A // A 的 this
            val b = this@B // B 的 this

            val c = this // foo() 的接收者蚕涤,一個 Int
            val c1 = this@foo // foo() 的接收者,一個 Int

            val funLit = lambda@ fun String.() {
                val d = this // funLit 的接收者
            }

            val funLit2 = { s: String ->
                // foo() 的接收者茴丰,因為它包含的 lambda 表達式
                // 沒有任何接收者
                val d1 = this
            }
        }
    }
}

typealias

看名字可以猜測帶有別名的作用,是的峦椰,typealias 我們可以成為類型別名汰规,聽起來怪怪的,說起來其實很好理解冤竹,可以代理 interface 聲明一個單方法類型的接口鹦蠕,不能寫在 class 內(nèi)在抛,打擊理解一下,寫在 class 內(nèi)不就成了內(nèi)部類了肠阱,typealias 的有點在于可以非常省事的聲明一個類似接口出來

// 在 class 外聲明朴读,作用域和平常類一樣
typealias Click = (String, String) -> Int

class BookData(map: MutableMap<String, Any>) {

    fun my(click: Click) {
        click("GG", "AA")
    }

    fun test2() {
        // typealias 填參數(shù)時和函數(shù)式對象一樣
        my { name, age ->
            Log.d("AA", "my 方法參數(shù)傳入")
            return@my 10
        }
    }
}

let / apply / run / with

  • let 函數(shù) - 可以對指定對象提供一段代碼的執(zhí)行衅金,返回最后一行的對象,可以不是操作的對象

比如下面這段代碼鉴吹,let 接收我們新建的這個 book 對象惩琉,然后對 book 對象進行了操作,最后一行返回數(shù)據(jù)良蒸,這里可以不寫 return

        var book: Book = Book("88").let {
            it.name = "-77"
            it.sex = "99"
            return@let it
        }

返回不一樣的數(shù)據(jù)類型伍玖,我們接受 Book 類型的數(shù)據(jù),最后返回 String 類型的數(shù)據(jù)

        var name: String = Book("88").let {
            it.name = "-77"
            it.sex = "99"
            return@let it.name
        }

我么你還可以結(jié)合 ? 進行對 null 數(shù)據(jù)的操作

        Book("88")?.let {
            it.name = "-77"
            it.sex = "99"
            return@let it.name
        }
  • apply 函數(shù) - 可以對指定對象進行代碼擴展仔燕,然后返回這個對象,注意是這個對象五辽,意味著不能改變對象類型外恕,然后再配合 let 獲取這個對象再進行操作

比如下面這段代碼,apply 內(nèi)的代碼好比就是寫在 Book 類型里面的罪郊,所有屬性剛和方法直接掉尚洽,不像 let 還得寫 let ,這是本質(zhì)的不同

        var book: Book = Book("88")
                .apply {
                    name = "-77"
                    sex = "99"
                }
                .let {
                    it.name = "-77"
                    it.sex = "99"
                    return@let it
                }

let 好比 rxjava 的 map 癣疟,apple 好比 flatmap

  • run 函數(shù) - run 和 apple 差不多睛挚,區(qū)別的是 run 返回的不是這個對象急黎,而是最后一行的對象
        var name: String = Book("88")
                .run {
                    name = "-77"
                    sex = "99"
                    return@run this
                }.let {
                    it.name = "ABB"
                    return@let it.name
                }
        var name: String = with(Book("88"))
        {
            name = "-77"
            sex = "99"
            return@with this
        }.let {
            it.name = "ABB"
            return@let it.name
        }

in / out

JAVA 里 List<Object> 是不能轉(zhuǎn)換為 List<String> 的,但是在 koltin 中借助 in / out 就能實現(xiàn)

  • Kotlin 中的 out A 類似于 Java 中的 ? extends A遭贸,即泛型參數(shù)類型必須是 A或者 A 的子類心软,用來確定類型的上限
  • Kotlin 中的 in A 類似于 Java 中的 ? super A,即泛型參數(shù)類型必須是 B 或者 B 的父類耳贬,用來確定類型的下限
fun copy(from: List<out A>, to: List<in A>) {
    for (i in from.indices) {
        to[i] = from[i]
    }
}

return/break/continue

kotlin 的 return/break/continue 和 java 含義一樣猎唁,但是比 java 擴展的是可以用 @ 標價返回的位置,直接看例子腐魂,比說強

    // 1. 和Java不同的是,這些表達式都可作為更大表達式的一部分
    val s = person.name ?: return

    //2. 和Java不同的是削樊,在 Kotlin 中任何表達式都可以用 標簽@ 來標記
    loop@ for (i in 1..100) {
        for (j in 1..100) {
            if (……) break@loop // 終止loop標記的循環(huán)
            if (……) continue@loop // 跳出loop標記的循環(huán)漫贞,繼續(xù)下一次loop標記的循環(huán)
        }
    }

    // 3. 從外層函數(shù)返回
    fun foo() {
        ints.forEach {
            if (it == 0) return // 默認從foo(){}返回
            print(it)
        }
    }

    //4. 用顯式標簽從lambda表達式中返回
    fun foo() {
        ints.forEach lit@ {
            if (it == 0) return@lit // 標記從forEach{}返回
            print(it)
        }
    }

    // 5. 用隱式標簽(與接收lambda的函數(shù)同名)從lambda表達式中返回
    fun foo() {
        ints.forEach {
            if (it == 0) return@forEach // 隱式標簽forEach育叁,從forEach{}返回
            print(it)
        }
    }

    // 6. 用匿名函數(shù)替代lambda表達式:
    fun foo() {
        ints.forEach(fun(value: Int) {
            if (value == 0) return // 從該匿名函數(shù)fun返回
            print(value)
        })
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末擂红,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子树碱,更是在濱河造成了極大的恐慌变秦,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赎婚,死亡現(xiàn)場離奇詭異挣输,居然都是意外死亡福贞,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進店門完丽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來逻族,“玉大人骄崩,你說我怎么就攤上這事薄辅〕げ螅” “怎么了?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長枪芒。 經(jīng)常有香客問我,道長纽甘,這世上最難降的妖魔是什么抽碌? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任货徙,我火速辦了婚禮,結(jié)果婚禮上赏迟,老公的妹妹穿的比我還像新娘蠢棱。我一直安慰自己,他們只是感情好糕再,可當我...
    茶點故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布突想。 她就那樣靜靜地躺著,像睡著了一般蒿柳。 火紅的嫁衣襯著肌膚如雪漩蟆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天圾叼,我揣著相機與錄音,去河邊找鬼构挤。 笑死惕鼓,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的矾飞。 我是一名探鬼主播呀邢,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼申眼!你這毒婦竟也來了蝉衣?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤姻氨,失蹤者是張志新(化名)和其女友劉穎肴焊,沒想到半個月后功戚,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡届宠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年豌注,在試婚紗的時候發(fā)現(xiàn)自己被綠了灯萍。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡齿风,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出救斑,到底是詐尸還是另有隱情,我是刑警寧澤穷娱,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布鄙煤,位于F島的核電站茶袒,受9級特大地震影響凉馆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜向叉,卻給世界環(huán)境...
    茶點故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一母谎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧奇唤,春花似錦匹摇、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至堡妒,卻和暖如春屿脐,著一層夾襖步出監(jiān)牢的瞬間宪卿,已是汗流浹背万栅。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留休溶,地道東北人扰她。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓徒役,卻偏偏與公主長得像,于是被迫代替她去往敵國和親忧勿。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,507評論 2 359