給安卓開發者的 Kotlin tips

with & apply

打印以下字符

START
ABCDEFGHIJKLMNOPQRSTUVWXYZ
END
複製代碼

若是不考慮kotlin的特性直接寫成java風格的代碼以下java

fun alphabet(): String {
    val result = StringBuilder()
    result.append("START\n")
    for (letter in 'A'..'Z') {
        result.append(letter)
    }
    result.append("\nEND")
    return result.toString()
}
複製代碼

可使用with簡化代碼android

fun alphabet3(): String {
    return with(StringBuilder()) {
        append("START\n")
        for (letter in 'A'..'Z') {
            this.append(letter)
        }
        append("\nEND")
        toString()
    }
}
複製代碼

with函數內部經過this來訪問傳入的參數,這裏具體指的是StringBuilder()的結果,with的返回值就是花括號的最後一行,也就是StringBuilder.toString()ios

另外with裏面的this能夠省去bash

fun alphabet4() =
        with(StringBuilder()) {
            append("START\n")
            for (letter in 'A'..'Z') {
                this.append(letter)
            }
            append("\nEND")
            toString()
        }
複製代碼

apply

有一個對象app

class User {
    var firstName = ""
    var lastName = ""
    var age = 10
    var address = "SH"
}
複製代碼

如今要對其初始化,能夠像java那樣ide

var user2 = User()
user2.firstName = "chen"
user2.lastName = "si"
user2.age = 30
user2.address = "BJ"
複製代碼

kotlin中爲咱們提供了apply方法,方便一系列的初始化函數

var user = User().apply {
    firstName = "chen"
    lastName = "si"
    age = 30
    address = "BJ"
}
複製代碼

對一個TextView初始化就變成了這樣post

textView.apply { 
            text = "hello"
            textSize = 32f
            textAlignment = TEXT_ALIGNMENT_CENTER
        }
複製代碼

with vs apply

@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}
複製代碼

經過with的函數聲明能夠看出來,with接受兩個參數,而且在第二個參數裏面能夠直接訪問第一個參數的方法,with的返回值是第二個參數,也就是第二個代碼塊最後一行ui

@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}
複製代碼

apply函數是個擴展函數,接受一個參數,返回值就是參數自身。this

解構聲明

val (red, green, blue) = Color.RED
val (left, top, right, bottom) = Rect(10, 20, 30, 40)
val (x, y) = Point(10, 20)
複製代碼

顯式參數名及默認參數值

fun <T> joinToString(collection: Collection<T>, separator: String, prefix: String, postfix: String): String {
    val result = StringBuilder(prefix)
    for ((index, element) in collection.withIndex()) {
        if (index > 0) result.append(separator)
        result.append(element)
    }
    result.append(postfix)
    return result.toString()
}

    val list = listOf("one", "two", "three", "four", "five")
    // 不標明參數名
    println(joinToString(list, " - ", "[", "]"))

複製代碼

這段代碼把list按順序輸出,最前面加上"[" ,中間加上" - ",最後加上"]",輸出結果爲

[one - two - three - four - five]
複製代碼

這樣寫的問題在於要傳遞過多參數,固然能夠像java那樣提供多個重載版原本實現默認參數的目的,kotlin中直接提供了默認參數的語法

fun <T> joinToString2(collection: Collection<T>, separator: String = ", ", prefix: String = "", postfix: String = ""): String {
    val result = StringBuilder(prefix)
    for ((index, element) in collection.withIndex()) {
        if (index > 0) result.append(separator)
        result.append(element)
    }
    result.append(postfix)
    return result.toString()
}

println(joinToString2(list, " - "))
println(joinToString2(list, " , ", "["))

複製代碼

輸出爲

[one - two - three - four - five]
one - two - three - four - five
複製代碼

joinToString2(list, " - ")指定了separator的值爲 " - ",其餘沒有指定的均使用默認值。

joinToString2(list, " , ", "[")指定了separator和prefix,而postfix使用默認值。

另外可使用擴展函數進一步簡化代碼

fun <T> Collection<T>.joinToString3(separator: String = ", ", prefix: String = "", postfix: String = ""): String {
    val result = StringBuilder(prefix)
    for ((index, element) in withIndex()) {
        if (index > 0) result.append(separator)
        result.append(element)
    }
    result.append(postfix)
    return result.toString()
}
    
println(list.joinToString3("/"))

複製代碼

有個默認參數以後能夠大大簡化安卓中自定義view中構造函數

class MyLinearLayout2 constructor(
        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr)
複製代碼

運算符重載

class Point(val x: Int, val y: Int) {
    operator fun plus(other: Point): Point {
        return Point(x + other.x, y + other.y)
    }

    override fun toString(): String {
        return "Point(x=$x, y=$y)"
    }
}
複製代碼

重寫plus方法,前面加上operator關鍵字, 而後在函數裏面定義具體的實現,以後就可使用 加好直接操做對象了。

val point1 = Point(10, 10)
    val point2 = Point(4, 4)
    val point3 = point1 + point2

複製代碼

再看個示例:

val spanString = "Copyright".toSpannable()
        spanString.setSpan(StyleSpan(BOLD), 0, spanString.length, SPAN_INCLUSIVE_EXCLUSIVE)
        spanString.setSpan(UnderlineSpan(), 0, spanString.length, SPAN_INCLUSIVE_EXCLUSIVE)
        spanString.setSpan(StyleSpan(Typeface.ITALIC), 0, spanString.length, SPAN_INCLUSIVE_EXCLUSIVE)
        textView.setText(spanString)
複製代碼

這段代碼 給TextView的上顯示的文字一次加上 加粗,下劃線以及斜體的效果 藉助運算符重載和ktx這段代碼也能夠簡化

val spannable = "Eureka!!!!".toSpannable()
        spannable += StyleSpan(BOLD) // Make the text bold with +=
        spannable += UnderlineSpan() // Make the text underline with +=
        spannable += StyleSpan(ITALIC)
        textView.setText(spanString)
複製代碼

lambda

點擊事件通常這麼寫

button.setOnClickListener(object : View.OnClickListener {
    override fun onClick(v: View) {
        handleClick(v)
    }
})

複製代碼

有了lambda以後能夠這麼寫

button.setOnClickListener({ v ->
            {
                handleClick(v)
            }
        })
複製代碼

參數v能夠去掉,直接使用it代替

button.setOnClickListener({
            handleClick(it)
        })

複製代碼

若是最後一個參數是高階函數,能夠直接移出小括號裏面

button.setOnClickListener() {
            handleClick(it)
        }
        
複製代碼

若是隻有一個參數,而且這個參數是高階函數,能夠把小括號移除

button.setOnClickListener {
            handleClick(it)
        }

複製代碼

高階函數

看一下前面的一個擴展函數

fun <T> Collection<T>.joinToString3(separator: String = ", ", prefix: String = "", postfix: String = ""): String {
    val result = StringBuilder(prefix)
    for ((index, element) in withIndex()) {
        if (index > 0) result.append(separator)
        result.append(element)
    }
    result.append(postfix)
    return result.toString()
}
複製代碼

這個函數的問題在於擴展性較差,目前只能把element的toString的結果加到最終的輸出,若是想留出本身實現該怎麼辦呢? 這時候就須要高階函數出場了

/擴展函數
fun <T> Collection<T>.joinToString(separator: String = " ,", prefix: String = " ", postfix: String = " ", transform: (T) -> String = { it.toString() }): String {
    val result = StringBuilder(prefix)

    for ((index, element) in this.withIndex()) {
        if (index > 0) result.append(separator)
        result.append(transform(element))
    }
    result.append(postfix)
    return result.toString()
}

    var listOf = listOf("Chen", "Rui", "Feng")
    
    println(listOf.joinToString())
    println(listOf.joinToString { it.toLowerCase() })
    println(listOf.joinToString(transform = { it.toUpperCase() }))
複製代碼
Chen ,Rui ,Feng 
 chen ,rui ,feng 
 CHEN ,RUI ,FENG 
複製代碼

經過高階函數能夠把本身須要的輸出方式傳遞進去

再看個例子

data class SiteVisit(val path: String,
                     val duration: Double,
                     val os: OS)
                     
    enum class OS { WINDOWS, LINUX, MAC, IOS, ANDROID }
    
    val log = listOf(
        SiteVisit("/", 34.0, OS.WINDOWS),
        SiteVisit("/", 22.0, OS.MAC),
        SiteVisit("/login", 12.0, OS.WINDOWS),
        SiteVisit("/signup", 8.0, OS.IOS),
        SiteVisit("/", 16.3, OS.ANDROID)
    )
複製代碼

定義一個類SiteVisit 有三個參數,分別表示用戶訪問的路徑,訪問持續時間以及用戶的操做系統

如今要統計出操做系統是WINDOWS的用戶的平均訪問時間

log.filter { it.os == OS.WINDOWS }
            .map { it.duration }
            .average()
複製代碼

要統計出操做系統是MAC的用戶的平均訪問時間

log.filter { it.os == OS.MAC }
            .map { it.duration }
            .average()
複製代碼

若是統計IOS用戶的平均訪問時間呢?再寫一遍? 能夠抽取出一個函數出來

fun List<SiteVisit>.averageDurationFor(os: OS) =
        filter { os == it.os }
                .map { it.duration }
                .average()
複製代碼

調用的時候直接傳入操做系統類型就行

log.averageDurationFor(OS.WINDOWS)
    log.averageDurationFor(OS.MAC)
複製代碼

如今問題來了,

  • 統計出WINDOWSMAC用戶的平均訪問時間
  • 統計出IOS用戶訪問/login頁面的平均訪問時間

剛剛的函數就沒辦法使用了,這時候就須要高階函數了

fun List<SiteVisit>.averageDurationFor2(predicate: (SiteVisit) -> Boolean) =
        filter { predicate(it) }
                .map { it.duration }
                .average()

複製代碼

調用的時候直接把條件傳進去

//android and ios average time
    println(log.averageDurationFor2 { it.os == OS.ANDROID || it.os == OS.IOS })


    //ios & login page average time
    println(log.averageDurationFor2 { it.os == OS.WINDOWS && it.path == "/login" })
複製代碼

有了這個高階函數 最開始統計出操做系統是MAC的用戶的平均訪問時間的代碼能夠寫成這樣子

log.averageDurationFor2 { it.os == OS.MAC })
複製代碼

參考文檔:

Kotlin in action

相關文章
相關標籤/搜索