幾個特性,快速上手Kotlin

前言

由於工做須要,因此最近大量的時間都用在了對Kotlin的提高上。因此最近的文章基本都是關於Kotlin的了內容。html

這不是一個死扣細節的文章,而是一個幫助小夥伴們快速由Java(Android)轉向Kotlin的文章,所以更多的是一種語法與思想混在的模式。java

幾個特性,快速上手Kotlin面試

充分理解Kotlin,快速上手寫業務設計模式

快速切換至Kotlin for Android模式閉包

聊一聊Kotlin中的協程,真香ide

正文

很少扯淡,點進來的小夥伴們確定都是來學技術的,開搞。函數

1、屬性

各位,千萬不要由於標題的屬性,就以爲沒什麼養分,覺得我要說什麼var、val。不不不,往下看,kotlin中的屬性大有文章。post

1.一、可觀察屬性Observable

這個語言特性,是很是很是用意思,且實用的。不信?往下看,來一個demo:性能

class User {
    var name: String by Delegates.observable("<no name>") {
        prop, old, new ->
        println("$old -> $new")
    }
}

fun main() {
    val user = User()
    user.name = "first"
    user.name = "second"
}
複製代碼

效果好很差,我們看「療效」,運行結果以下:學習

是否是以爲有點意思?咱們name這個屬性的任何變化,都被觀察了。咱們能夠在回調函數中,「胡搞瞎搞」~至於怎麼搞?whatever。 有小夥伴可能想說,既然我監聽了屬性的變化,我能夠不以偷偷改些屬性呢?固然能夠,不過咱們須要下面這個函數。

1.二、vetoable

vetoable比較有意思,能夠理解爲一個hook函數,它接受一個用於判斷的表達式,知足這個表達式的纔會被賦值,不然丟棄。很簡單,上demo:

// 過濾不知足條件的set
var max: Int by Delegates.vetoable(0) { property, oldValue, newValue ->
    newValue > oldValue
}

println(max) // 0

max = 10
println(max) // 10

max = 5
println(max) // 10
複製代碼

2.一、延遲屬性 Lazy

官方解釋(羅裏吧嗦,不看也罷) lazy() 是接受一個 lambda 並返回一個 Lazy 實例的函數,返回的實例能夠做爲實現延遲屬性的委託: 第一次調用 get() 會執行已傳遞給 lazy() 的 lambda 表達式並記錄結果, 後續調用 get() 只是返回記錄的結果。

說白了就是懶加載。被lazy標識的變量,在被調用的時候會觸發咱們實現的表達式,

(下文會重點聊表達式,這裏咱們只須要以後,表達式的最後一行,表明着return)

並拿到表達式的返回值。但表達式只會執行一次,後續的調用,直接回去表達式的返回值。也就是咱們常說的懶加載。

val lazyValue: String by lazy {
    println("computed!")
    "Hello"
}

fun main() {
    println(lazyValue)
    println(lazyValue)
}
複製代碼

看結果咱們就很清晰了吧。

委託

上邊說的這些東西,在Kotlin之中都統一稱之爲委託/委託屬性。委託是比較好用的一種語言特性,甚至能夠很巧妙的幫咱們解決一些複雜的設計模式上的問題。 這其中的有趣,還望小夥伴們本身去探索呦~


2、表達式

對於個人學習來講,表達式的不理解,最開始對我閱讀代碼形成了很大的困惑。主要是少了「相濡以沫」的return,搞得本身有點懵。因此這裏,讓咱們聊一聊表達式,也算填了上文挖的坑。

1.一、if表達式

if這個關鍵字,在Kotlin中表明一個表達式,它會默認有一個return,就是表達式中的最後一行。好比:

var num = if(...某個判斷){
    666
}else{
    66666
}
複製代碼

這裏num的值就是666。既然咱們的if有自帶return的功能,那麼咱們Java中經常使用的?:(三元運算符)是否是就沒辦法用了?的確要三元運算符(條件 ? 而後 : 不然)在Kotlin中換了寫法(但並不是不能用),由於普通的 if 就能勝任這個角色。

// 做爲表達式
val max = if (a > b) a else b

val max = if (a > b) {
    print("Choose a")
    a
} else {
    print("Choose b")
    b
}
複製代碼

?:在Kotlin中表示: 若是左側的值爲空,就取右側的值。

更多有趣的符號用法,能夠參考官網:www.kotlincn.net/docs/refere…

1.二、關於return

既然咱們從if中,瞭解了if中的隱式return,那這裏可能會有一個疑問,能不能顯示的寫一個return呢?答案是:不行。 由於在Kotlin中,return的語義是這樣的:從最直接包圍它的函數或者匿名函數返回。

表示式不屬於函數,因此不行,一樣Lambda表達式也不行。不這裏有些特殊狀況,因此咱們好好聊一聊:

要退出一個 Lambda 表達式,咱們必須使用一個標籤,而且在 Lambda 表達式內部禁止使用裸 return,由於 Lambda 表達式不能使包含它的函數返回:

// 這裏forEach是一個Lambda表達式,咱們使用**標籤**的形式,使其return
listOf(1, 2, 3, 4, 5).forEach lit@{
    if (it == 3) return@lit // 局部返回到該 lambda 表達式的調用者,即 forEach 循環
    print(it)
}
複製代碼

這裏確定有小夥伴質疑:我能夠在forEach裏直接return啊!沒錯,的確是能夠。由於forEach內聯函數。內聯函數是能夠return的:

關於內聯函數後文會有篇幅展開它。

官方介紹:內聯是被容許的(這種返回(位於 lambda 表達式中,但退出包含它的函數)稱爲非局部返回。)

inline fun inlined(block: () -> Unit) {
    println("hi!")
}
fun foo() {
    inlined {
        return // OK:該 lambda 表達式是內聯的
    }
}
fun main() {
    foo()
}
複製代碼

好比,這種return是合法的,由於foreach是內聯函數

fun hasZeros(ints: List<Int>): Boolean {
    ints.forEach {
        if (it == 0) return true // 從 hasZeros 返回
    }
    return false
}

public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
    for (element in this) action(element)
}
複製代碼

嘚吧嘚說了這麼,平常能用到麼?說實話,沒有diao用。不過,遇到了我們知道該如何解釋,這也算是一種收穫吧。

2.一、When 表達式

when是咱們經常使用的switch的加強plus版。其最簡單的形式以下:

when (x) {
    1 -> print("x == 1")
    2 -> print("x == 2")
    else -> { // 注意這個塊
        print("x is neither 1 nor 2")
    }
}
複製代碼

咱們也能夠檢測一個值在(in)或者不在(!in)一個區間或者集合中。即知足某些條件:

when (x) {
    in 1..10 -> print("x is in the range")
    in validNumbers -> print("x is valid")
    !in 10..20 -> print("x is outside the range")
    else -> print("none of the above")
}
複製代碼

另外一種用法是檢測一個值是(is)或者不是(!is)一個特定類型的值。

因爲智能轉換,咱們能夠訪問該類型的方法與屬性而無需任何額外的檢測。

fun hasPrefix(x: Any) = when(x) {
    is String -> x.startsWith("prefix")
    else -> false
}
複製代碼

由於,說when是switch的plus不爲過吧。更多有趣的用法,歡迎各位小夥伴留言補充呦~


3、雜亂的小細節

1.一、Class

val c = MyClass::class
複製代碼

返回的是KClass,若是咱們須要Class,則要這樣MyClass::class.java

1.二、this

要訪問來自外部做用域的this(一個類 或者擴展函數, 或者帶標籤的帶有接收者的函數字面值)咱們使用this@label,其中 @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
            }
        }
    }
}
複製代碼

1.三、匿名函數

var list = arrayListOf(1, 2, 3, 4, 5, 6)
        // lambda表達式
        list.filter {
            it > 3
        }
        // 規規矩矩的匿名函數
        list.filter(fun(it): Boolean {
            return it > 3
        })
        // 自動類型推斷後簡寫的匿名函數
        list.filter(fun(it) = it > 3)
  
複製代碼

對於具備表達式函數體的匿名函數將自動推斷返回類型,而具備代碼塊函數體的返回類型必須顯式指定(或者已假定爲 Unit) 匿名函數參數老是在括號內傳遞。 容許將函數留在圓括號外的簡寫語法僅適用於 lambda 表達式。

Lambda表達式與匿名函數之間的另外一個區別是非局部返回的行爲。一個不帶標籤的 return 語句老是在用 fun 關鍵字聲明的函數中返回。這意味着 lambda 表達式中的 return 將從包含它的函數返回,而匿名函數中的 return 將從匿名函數自身返回。

1.四、內聯函數

使用高階函數(關於高階函數,能夠看我以前的一篇文章)會帶來一些運行時的效率損失。由於每個函數都是一個對象,而且會捕獲一個閉包( 即那些在函數體內會訪問到的變量)。 咱們都清楚,內存分配(對於函數對象和類)和虛擬調用會引入運行時的開銷。因此此時就須要內聯函數,來消除這種開銷。 官方使用了lock這個函數來解釋這個問題:

fun <T> lock(lock: Lock, body: () -> T): T {
  lock.lock()
  try {
    return body()
  }
  finally {
    lock.unlock()
  }
}
複製代碼

正常咱們會這樣調用這個函數lock(l) { foo() }。若是不加修飾的話,由於高階函數的存在,會形成大量的對象對建立出來,因此咱們這裏咱們須要用內聯的方式消除這種額外。

// 使用內聯,代碼編譯後將變成這樣
l.lock()
try {
    foo()
}
finally {
    l.unlock()
}
複製代碼

可能有小夥伴在這裏會有一些懵逼,一時沒有理解內聯作了什麼。接下來用三個函數,解釋一下這個問題:

// 一個函數中,調用了另外一個函數。虛擬機勢必要爲funtion1的調用,增長不少開銷。
fun funtion(){
    var a =1+1+1
    funtion1()
}

fun funtion1(){
    var aa =1+1+1
    var bb =1+1+1
    var cc =1+1+1
}

// 那麼咱們使用內聯以後,就變成了這樣:
fun funtion(){
    var a =1+1+1
    var aa =1+1+1
    var bb =1+1+1
    var cc =1+1+1
}
複製代碼

咱們能夠看到,內聯以後,咱們funtion1函數中的實現,彷彿就在funtion函數中同樣。所以就相應的下降了這部分的消耗。

內聯可能致使生成的代碼增長;不過若是咱們使用得當(即避免內聯過大函數),性能上會有所提高,尤爲是在循環中的「超多態(megamorphic)」調用處。

尾聲

OK,到此差很少關於Kotlin的基於語法的內容差很少就要告一段落了。接下來的文章基本就會圍繞着Android中的Kotlin進行開展。 但願各位小夥伴們能夠從中有所收穫呦~

我是一個應屆生,最近和朋友們維護了一個公衆號,內容是咱們在從應屆生過渡到開發這一路所踩過的坑,以及咱們一步步學習的記錄,若是感興趣的朋友能夠關注一下,一同加油~

我的公衆號:IT面試填坑小分隊
相關文章
相關標籤/搜索