kotlin 之 函數(shù)的定義與調(diào)用

在Kotlin中創(chuàng)建集合

上一章我們已經(jīng)使用setOf函數(shù)創(chuàng)建一個set了巍扛。同樣的,我們也可以用類似的方法創(chuàng)建一個list或者map:

val set = setOf(1, 2, 3)
val list = listOf(1, 2, 3)
val map = mapOf(1 to "one", 2 to "two")

to 并不是一個特殊的結(jié)構(gòu)弥臼,而是一個普通函數(shù)寿酌,在后面會繼續(xù)探討它饮醇。
有沒有想過這里創(chuàng)建出來的set毕源、list、map到底是什么類型的那陕习?可以通過.javaClass 屬性獲取類型霎褐,相當(dāng)于Java中的getClass() 方法:

println(set.javaClass)
println(list.javaClass)
println(map.javaClass)

//輸出
class java.util.LinkedHashSet
class java.util.Arrays$ArrayList
class java.util.LinkedHashMap

可以看到都是標(biāo)準(zhǔn)的Java集合類,Kotlin沒有自己專門的集合類衡查,是為了更容易與Java代碼交互瘩欺,當(dāng)從Kotlin中調(diào)用Java函數(shù)的時候,不用轉(zhuǎn)換它的集合類來匹配Java的類拌牲,反之亦然俱饿。
盡管Kotlin的集合類和Java的集合類完全一致,但Kotlin還不止于此塌忽。舉個例子拍埠,可以通過以下方法來獲取一個列表中最后一個元素,或者得到一個數(shù)字列表的最大值:

val strings = listOf("first", "second", "fourteenth")
println(strings.last())
val numbers = setOf(1, 14, 2)
println(numbers.max())

//輸出
fourteenth
14

或許你應(yīng)該知道last()max() 在Java的集合類中并不存在土居,這應(yīng)該是Kotlin自己擴展的方法枣购,可以你要知道上面我們打印出來的類型明確是Java中的集合類,但在這里調(diào)用方法的對象就是這些集合類擦耀,又是怎么做到讓一個Java中的類調(diào)用它本身沒有的方法那棉圈?在后面我們講到擴展函數(shù)的時候你就會知道了!

讓函數(shù)更好調(diào)用

現(xiàn)在我們知道了如何創(chuàng)建一個集合眷蜓,接下來讓我們打印它的內(nèi)容分瘾。Java的集合都有一個默認(rèn)的toString 實現(xiàn),但它的何世華的輸出是固定的吁系,而且往往不是你需要的樣子:

val list = listOf(1, 2, 3)
println(list) //觸發(fā)toString的調(diào)用

//輸出
[1, 2, 3]

假設(shè)你需要用分號來分隔每一個元素德召,然后用括號括起來,而不是采用默認(rèn)實現(xiàn)汽纤。要解決這個問題上岗,Java項目會使用第三方庫,比如Guava和Apache Commons蕴坪,或者是在這個項目中重寫打印函數(shù)肴掷。在Kotlin中,它的標(biāo)準(zhǔn)庫中有一個專門的函數(shù)來處理這種情況背传。
但是這里我們先不借助Kotlin的工具捆等,而是自己寫實現(xiàn)函數(shù):

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()
}

這個函數(shù)是泛型,它可以支持元素為任意類型的集合续室。讓我們來驗證一下,這個函數(shù)是否可行:

val list = listOf(1, 2, 3)
println(joinToString(list, ";", "(", ")"))

//輸出
(1;2;3)

看來是可行的谒养,接下來我們要考慮的是如何修改讓這個函數(shù)的調(diào)用更加簡潔呢挺狰?畢竟每次調(diào)用都要傳入四個參數(shù)也是挺麻煩的明郭。

命名參數(shù)

我們關(guān)注的第一個問題就是函數(shù)的可讀性。就以joinToString 來看:

joinToString(list, "", "", "")

你能看得出這些String都對應(yīng)什么參數(shù)嗎丰泊?可能必須要借助IDE工具或者查看函數(shù)說明或者函數(shù)本身才能知道這些參數(shù)的含義薯定。
在Kotlin中,可以做的更優(yōu)雅:

println(joinToString(list, separator = "", prefix = "", postfix = ""))

當(dāng)你調(diào)用一個Kotlin定義的函數(shù)時瞳购,可以顯示得標(biāo)明一些參數(shù)的名稱话侄。如果在調(diào)用一個函數(shù)時,指明了一個參數(shù)的名稱学赛,為了避免混淆年堆,那它之后的所有參數(shù)都需要標(biāo)明名稱。

當(dāng)你在Kotlin中調(diào)用Java定義的函數(shù)時盏浇,不能采用命名參數(shù)变丧。因為把參數(shù)名稱存到 .class文件是Java8以及更高版本的一個可選功能,而Kotlin需要保持和Java6的兼容性绢掰。

可能到這里你只是覺得命名參數(shù)讓函數(shù)便于理解痒蓬,但是調(diào)用變得復(fù)雜了,我還得多寫參數(shù)的名稱滴劲!別急攻晒,與下面說的默認(rèn)參數(shù)相結(jié)合時,你就知道命名參數(shù)的好了班挖。

默認(rèn)參數(shù)值

Java的另一個普遍存在問題是:一些類的重載函數(shù)實在太多了鲁捏。這些重載大多是為了向后兼容,方便API的使用者聪姿,最終導(dǎo)致的結(jié)果是重復(fù)碴萧。
在Kotlin中,可以在聲明函數(shù)的時候末购,指定參數(shù)的默認(rèn)值破喻,這樣可以避免創(chuàng)建重載的函數(shù)。讓我們嘗試改進(jìn)一下前面的joinToString 函數(shù)盟榴。在大多數(shù)情況下曹质,我們可能只會改變分隔符或者改變前后綴,所以我們把這些設(shè)置為默認(rèn)值:

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()
}

現(xiàn)在在調(diào)用一下這個函數(shù)擎场,可以省略掉有默認(rèn)值的參數(shù)羽德,效果就像在Java中聲明的重載函數(shù)一樣。

println(joinToString(list))
println(joinToString(list, ";"))

//輸出
1,2,3
1;2;3

當(dāng)你使用常規(guī)的調(diào)用語法時迅办,必須按照函數(shù)申明中定義的參數(shù)順序來給定參數(shù)宅静,可以省略的只有排在末尾的參數(shù)。如果使用命名參數(shù)站欺,可以省略中的一些參數(shù)姨夹,也可以以你想要的任意 順序只給定你需要的參數(shù):

//打亂了參數(shù)順序纤垂,并且separator參數(shù)使用了默認(rèn)值
println(joinToString(prefix = "{", collection = list, postfix = "}"))

//輸出
{1,2,3}

注意,參數(shù)的默認(rèn)值是被編譯到被調(diào)用的函數(shù)中磷账,而不是調(diào)用的地方峭沦。如果你改變了參數(shù)默認(rèn)值并重新編譯這個函數(shù),沒有給參數(shù)重新賦值的調(diào)用者逃糟,將會開始使用新的默認(rèn)值吼鱼。

消除靜態(tài)工具類:頂層函數(shù)和屬性

Java作為一門面對對象的語言,需要所有的代碼都寫作類的函數(shù)绰咽。但實際上項目中總有一些函數(shù)不屬于任何一個類菇肃,最終產(chǎn)生了一些類不包含任何狀態(tài)或者實例函數(shù),僅僅是作為一堆靜態(tài)函數(shù)的容器剃诅。在JDK中巷送,最明顯的例子應(yīng)該就是Collections了,還有你的項目中是不是有很多以Util作為后綴的類矛辕?
在Kotlin中笑跛,根本不需要去創(chuàng)建這些無意義的類,你可以把這些函數(shù)直接放到代碼文件的頂層聊品,不用從屬于任何類飞蹂。事實上joinToString函數(shù)之前就是直接定義在Join.kt 文件。

package com.huburt.imagepicker

@JvmOverloads
fun <T> joinToString(...): String {...}

這會怎樣運行呢翻屈?當(dāng)編譯這個文件的時候陈哑,會生成一些類,因為JVM只能執(zhí)行類中的代碼伸眶。當(dāng)你在使用Kotlin的時候惊窖,知道這些就夠了。但是如果你需要從Java中來調(diào)用這些函數(shù)厘贼,你就必須理解它將怎樣被編譯界酒,來看下編譯后的類是怎樣的:

package com.huburt.imagepicker

public class JoinKt {
    public static String joinToString(...){...}
}

可以看到Kotlin編譯生成的類的名稱,對應(yīng)于包含函數(shù)的文件名稱嘴秸,這個文件中的所有頂層函數(shù)編譯為這個類的靜態(tài)函數(shù)毁欣。因此,當(dāng)從Java調(diào)用這個函數(shù)的時候岳掐,和調(diào)用任何其他靜態(tài)函數(shù)一樣簡單:

import com.huburt.imagepicker.JoinKt

JoinKt.joinToString(...)

修改文件類名

是不是覺得Kt結(jié)尾的類使用起來很別扭凭疮,Kotlin提供了方法改變生成類的類名,只需要為這個kt文件添加@JvmName的注解串述,將其放到這個文件的開頭执解,位于包名的前面:

@file:JvmName("Join") //指定類名
package com.huburt.imagepicker

@JvmOverloads
fun <T> joinToString(...): String {...}

現(xiàn)在就可以用新的類名調(diào)用這個函數(shù):

import com.huburt.imagepicker.Join

Join.joinToString(...)

頂層屬性

和函數(shù)一樣,屬性也可以放到文件的頂層纲酗。從Java的角度來看就是靜態(tài)屬性材鹦,沒啥特別的,而且由于沒有了類的存在逝淹,這種屬性用到的機會也不多。
需要注意的是頂層函數(shù)和其他任意屬性一樣桶唐,默認(rèn)是通過訪問器暴露給Java使用的(也就是通過getter和setter方法)。為了方便使用茉兰,如果你想要把一個常量以public static final 的屬性暴露給Java尤泽,可以用const 來修飾屬性:

const val TAG = "tag"

這樣就等同與Java的:

public static final String TAG = "tag"

給別人的類添加方法:擴展函數(shù)和屬性

Kotlin的一大特色就是可以平滑的與現(xiàn)有代碼集成。你可以完全在原有的Java代碼基礎(chǔ)上開始使用Kotlin规脸。對于原有的Java代碼可以不修改源碼的情況下擴展功能:擴展函數(shù)坯约。這一點是我認(rèn)為Kotlin最強大的地方了。
擴展函數(shù)非常簡單莫鸭,它就是一個類的成員函數(shù)闹丐,不過定義在類的外面。為了方便闡述被因,讓我們添加一個方法卿拴,來計算一個字符串的最后一個字符:

package strings

    //String ->接收者類型               //this ->接收者類型
fun String.lastChar(): Char = this.get(this.length - 1)

復(fù)制代碼你所要做的,就是把你要擴展的類或者接口的名稱梨与,放到即將添加的函數(shù)前面堕花,這個類的名稱被稱為接收者類型;用來調(diào)用這個擴展函數(shù)的那個對象粥鞋,叫做接收者對象缘挽。
接著就可以像調(diào)用類的普通成員函數(shù)一樣去調(diào)用這個函數(shù)了:

println("Kotlin".lastChar())
//輸出
n

在這個例子中,String就是接收者類型呻粹,而“Kotlin”就是接收者對象壕曼。
在這個擴展函數(shù)中,可以像其他成員函數(shù)一樣用this等浊,也可以像普通函數(shù)一樣省略它:

package strings

fun String.lastChar(): Char = get(length - 1) //省略this調(diào)用string對象其他函數(shù)

導(dǎo)入擴展函數(shù)

對于你定義的擴展函數(shù)腮郊,它不會自動的在整個項目范圍內(nèi)生效。如果你需要使用它凿掂,需要進(jìn)行導(dǎo)入伴榔,導(dǎo)入單個函數(shù)與導(dǎo)入類的語法相同:

import strings.lastChar

val c = "Kotlin".lastChar()

當(dāng)然也可以用表示文件下所有內(nèi)容:import strings.

另外還可以使用as 關(guān)鍵字來修改導(dǎo)入的類或則函數(shù)的名稱:

import strings.lastChar as last

val c = "Kotlin".last()

在導(dǎo)入的時候重命名可以解決函數(shù)名重復(fù)的問題。

從Java中調(diào)用擴展函數(shù)

實際上庄萎,擴展函數(shù)是靜態(tài)函數(shù)踪少,它把調(diào)用對象作為函數(shù)的第一個參數(shù)。在Java中調(diào)用擴展函數(shù)和其他頂層函數(shù)一樣糠涛,通過.kt文件生成Java類調(diào)用靜態(tài)的擴展函數(shù)援奢,把接收者對象傳入第一個參數(shù)即可。例如上面提到的lastChar擴展函數(shù)是定義在StringUtil.kt中忍捡,在Java中就可以這樣調(diào)用:

char c = StringUtilKt.lastChar("Java")

作為擴展函數(shù)的工具函數(shù)

現(xiàn)在我們可以寫一個joinToString 函數(shù)的終極版本了集漾,它和你在Kotlin標(biāo)準(zhǔn)庫中看到的一模一樣:

@JvmOverloads
fun <T> Collection<T>.joinToString(
        separator: String = ",",
        prefix: String = "",
        postfix: String = ""
): String {
    val result = StringBuilder(prefix)
    for ((index, element) in this.withIndex()) { //this是接收者對象切黔,即T的集合
        if (index > 0) result.append(separator)
        result.append(element)
    }
    result.append(postfix)
    return result.toString()
}

//使用
println(list.joinToString())
println(list.joinToString(";"))
println(list.joinToString(prefix = "{", postfix = "}"))

將原來的參數(shù)Collection,提出來具篇,作為接收者類型編寫的擴展函數(shù)纬霞,使用方法也像是Collection類的成員函數(shù)一樣了(當(dāng)然Java調(diào)用還是靜態(tài)方法,第一個參數(shù)傳入Collection對象)驱显。

不可重寫的擴展函數(shù)

先來看一個重寫的例子:

//Kotlin中class默認(rèn)是final的诗芜,如果需要繼承需要修飾open,函數(shù)也相同
open class View {
    open fun click() = println("View clicked")
}

class Button : View() { //繼承
    override fun click() = println("Button clicked")
}

當(dāng)你聲明了類型為View的變量埃疫,那它可以被賦值為Button類型的對象伏恐,因為Button是View的一個子類。當(dāng)你在調(diào)用這個變量的一般函數(shù)栓霜,比如click的時候翠桦,如果Button復(fù)寫了這個函數(shù),name這里將會調(diào)用到Button中復(fù)寫的函數(shù):

val view: View = Button()
view.click()

//輸出
Button clicked

但是對于擴展函數(shù)來說胳蛮,并不是這樣的销凑。擴展函數(shù)并不是類的一部分,它是聲明在類之外的鹰霍。盡管可以給基類和子類都分別定義一個同名的擴展函數(shù)闻鉴,當(dāng)這個函數(shù)被調(diào)用時,它會用到哪一個呢茂洒?這里孟岛,它是由該變量的靜態(tài)類型所決定的,而不是這個變量的運行時類型督勺。

fun View.showOff() = println("i'm a view!")

fun Button.showOff() = println("i'm a button!")

val view: View = Button()
view.click()

//輸出
i'm a view!

當(dāng)你在調(diào)用一個類型為View的變量的showOff函數(shù)時渠羞,對應(yīng)的擴展函數(shù)會被調(diào)用,盡管實際上這個變量現(xiàn)在是一個Button對象智哀〈窝回想一下,擴展函數(shù)會在Java中編譯為靜態(tài)函數(shù)瓷叫,同時接受值將會作為第一個參數(shù)屯吊。這樣其實2個showOff擴展函數(shù)就是不同參數(shù)的靜態(tài)函數(shù),

View view = new Button();
XxKt.showOff(view);  //定義在Xx.kt文件中

參數(shù)的類型決定了調(diào)用那個靜態(tài)函數(shù)摹菠,想要調(diào)用Button的擴展函數(shù)盒卸,則必須先將參數(shù)轉(zhuǎn)成Button類型才行:XxKt.showOff((Button)view);

因此,擴展函數(shù)也是有局限性的次氨,擴展函數(shù)是能擴展蔽介,即定義新的函數(shù),而不能重寫改變原有函數(shù)的實現(xiàn)(本質(zhì)是一個靜態(tài)函數(shù))。如果定了一個類中本身存在成員函數(shù)同名的擴展函數(shù)虹蓄,Kotlin種調(diào)用該方法的時候會如何呢犀呼?(Java中沒有這個顧慮,調(diào)用方式不同)

open class View {
    open fun click() = println("View clicked")
}

fun View.click() = println("擴展函數(shù)")

val view = View()
view.click()

//輸出
View clicked

明顯了吧~ 對于有同名成員函數(shù)和擴展函數(shù)時薇组,在Kotlin中調(diào)用始終執(zhí)行成員函數(shù)的代碼外臂,擴展函數(shù)并不起作用,相當(dāng)于沒有定義体箕。這一點在實際開發(fā)中需要特別注意了专钉!

擴展屬性

擴展屬性提供了一種方法,用于擴展類的API累铅,可以用來訪問屬性,用的是屬性語法而不是函數(shù)的語法站叼。盡管他們被稱為屬性娃兽,但它們可以沒有任何狀態(tài),因為沒有合適的地方來存儲它尽楔,不可能給現(xiàn)有的Java對象的實例添加額外的字段投储。舉個例子吧:

val String.lastChar: Char
    get() = get(length - 1)

同樣是獲取字符串的最后一個字符,這次是用擴展屬性的方式定義阔馋。擴展屬性也像接收者的一個普通成員屬性一樣玛荞,這里必須定義getter函數(shù),因為沒有支持字段呕寝,因此沒有默認(rèn)的getter的實現(xiàn)勋眯。同理,初始化也不可以:因為沒有地方存儲初始值下梢。
剛剛定義是一個val的擴展屬性客蹋,也可以定義var屬性:

var StringBuilder.lastChar: Char
    get() = get(length - 1)
    set(value) {
        setCharAt(length - 1, value)
    }

還記得上一篇文章的自定義訪問器的內(nèi)容嗎?這里的定義方式與自定義訪問器一致孽江,val 屬性不可變讶坯,因此只需要定義getter,而var 屬性可變岗屏,所以getter和setter都需要辆琅。
可能不是很好理解擴展屬性,或者會和真正的屬性混淆这刷,下面列出了擴展屬性轉(zhuǎn)換成Java的代碼婉烟,你就會比較直觀的理解了。

   public static final char getLastChar(@NotNull String $receiver) {
      return $receiver.charAt($receiver.length() - 1);
   }

   public static final char getLastChar(@NotNull StringBuilder $receiver) {
      return $receiver.charAt($receiver.length() - 1);
   }

   public static final void setLastChar(@NotNull StringBuilder $receiver, char value) {
      $receiver.setCharAt($receiver.length() - 1, value);
   }

和擴展函數(shù)是相同的崭歧,僅僅是靜態(tài)函數(shù):提供獲取lastChar的功能隅很,這樣的定義方式可以在Kotlin中像使用普通屬性的調(diào)用方式來使用擴展屬性,給你一種這是屬性的感覺,但本質(zhì)上在Java中就是靜態(tài)函數(shù)叔营。

處理集合:可變參數(shù)澡谭、中綴調(diào)用和庫的支持

擴展Java集合的API

val strings = listOf("first", "second", "fourteenth")
println(strings.last())
val numbers = setOf(1, 14, 2)
println(numbers.max())

還記的之前我們使用上面的方式獲取了list的最后一個元素,以及set中的最大值望门。到這里你可能已經(jīng)知道了甫菠,last()max() 都是擴展函數(shù),自己點進(jìn)方法驗證一下吧婴谱!

可變參數(shù)

如果你也看了listOf 函數(shù)的定義蟹但,你一定看到了這個:

public fun <T> listOf(vararg elements: T): List<T>

也就是vararg 關(guān)鍵字,這讓函數(shù)支持任意個數(shù)的參數(shù)谭羔。在Java中同樣的可變參數(shù)是在類型后面跟上... 华糖,上面的方法在Java則是:

public <T> List<T> listOf(T... elements)

但是Kotlin的可變參數(shù)相較于Java還是有點區(qū)別:當(dāng)需要傳遞的參數(shù)已經(jīng)包裝在數(shù)組中時,調(diào)用該函數(shù)的語法瘟裸,在Java中可以按原樣傳遞數(shù)組客叉,而Kotlin則要求你顯示地解包數(shù)組,以便每個數(shù)組元素在函數(shù)中能作為單獨的參數(shù)來調(diào)用话告。從技術(shù)的角度來講兼搏,這個功能被稱為展開運算符,而使用的時候沙郭,不過是在對應(yīng)的參數(shù)前面放一個*

val array = arrayOf("a", "b")
val list = listOf("c", array)
println(list)
val list2 = listOf<String>("c", *array)
println(list2)

//輸出
[c, [Ljava.lang.String;@5305068a]
[c, a, b]

通過對照可以看到佛呻,如果不加*,其實是把數(shù)組對象當(dāng)做了集合的元素病线。加上* 才是將數(shù)組中所有元素添加到集合中吓著。listOf也可以指定泛型<String> ,你可以嘗試在listOf("c", array) 這里加泛型氧苍,第二個參數(shù)array就會提示類型不正確夜矗。
Java中沒有展開,我們也可以調(diào)用Kotlin的listOf函數(shù)让虐,該函數(shù)聲明在Collections.kt文件下:

List<String> strings = CollectionsKt.listOf(array);
System.out.println(strings);
//List<String> strings = CollectionsKt.listOf("c", array);//無法編譯

//輸出
[a, b]

Java中可以直接傳入數(shù)組紊撕,但是不能同時傳入單個元素和數(shù)組。

鍵值對的處理:中綴調(diào)用和解構(gòu)聲明

還記得創(chuàng)建map的方式嗎赡突?

val map = mapOf(1 to "one", 7 to "seven", 52 to "fifty-five")

之前說過to并不是一個內(nèi)置的結(jié)構(gòu)对扶,而是一種特殊的函數(shù)調(diào)用,被稱為中綴調(diào)用惭缰。
在中綴調(diào)用中浪南,沒有添加額外的分隔符,函數(shù)名稱是直接放在目標(biāo)對象名稱和參數(shù)之間的漱受,以下兩種調(diào)用方式是等價的:

1.to("one")//普通調(diào)用
1 to "one" //中綴調(diào)用

中綴調(diào)用可以與只有一個參數(shù)的函數(shù)一起使用络凿,換句話說就是只要函數(shù)只有一個參數(shù),都可以支持在Kotlin中的中綴調(diào)用,無論是普通的函數(shù)還是擴展函數(shù)絮记。要允許使用中綴符號調(diào)用函數(shù)摔踱,需要使用infix 修飾符來標(biāo)記它。例如to函數(shù)的聲明:

public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)

to函數(shù)會返回一個Pair類型的對象怨愤,Pair是Kotlin標(biāo)準(zhǔn)庫中的類派敷,它是用來表示一對元素。我們也可以直接用Pair的內(nèi)容來初始化兩個變量:

val (number, name) = 1 to "one"

這個功能稱之為解構(gòu)聲明撰洗,1 to "one" 會返回一個Pair對象篮愉,Pair包含一對元素,也就是1和one差导,接著又定義了變量(number, name) 分別指向Pair中的1和one试躏。
解構(gòu)聲明特征不止用于Pair。還可以使用map的key和value內(nèi)容來初始化兩個變量设褐。并且還適用于循環(huán)冗酿,正如你在使用的withIndex 函數(shù)的joinToString實現(xiàn)中看到的:

for ((index, element) in collection.withIndex()) {
    println("$index, $element")
}

to 函數(shù)是一個擴展函數(shù),可以創(chuàng)建一對任何元素络断,這意味著它是泛型接受者的擴展:可以使用1 to "one""one" to 1 项玛、list to list.size()等寫法貌笨。我們來看看mapOf 函數(shù)的聲明:

public fun <K, V> mapOf(vararg pairs: Pair<K, V>): Map<K, V>

listOf 一樣,mapOf 接受可變數(shù)量的參數(shù)襟沮,但這次他們應(yīng)該是鍵值對锥惋。盡管在Kotlin中創(chuàng)建map可能看起來像特殊的解構(gòu),而它不過是一個具有簡明語法的常規(guī)函數(shù)开伏。

字符串和正則表達(dá)式的處理

Kotlin定義了一系列擴展函數(shù)膀跌,使標(biāo)準(zhǔn)Java字符串使用起來更加方便。

分割字符串

Java中我們會使用String的split方法分割字符串固灵。但有時候會產(chǎn)生一些意外的情況捅伤,例如當(dāng)我們這樣寫"12.345-6.A".split(".") 的時候,我們期待的結(jié)果是得到一個[12, 345-6, A]數(shù)組巫玻。但是Java的split方法竟然返回一個空數(shù)組丛忆!這是應(yīng)為它將一個正則表達(dá)式作為參數(shù),并根據(jù)表達(dá)式將字符串分割成多個字符串仍秤。這里的點(.)是表示任何字符的正則表達(dá)式熄诡。
在Kotlin中不會出現(xiàn)這種令人費解的情況,因為正則表達(dá)式需要一個Regex類型承載诗力,而不是String凰浮。這樣確保了字符串不會被當(dāng)做正則表達(dá)式。

println("12.345-6.A".split("\\.|-".toRegex())) //顯示地創(chuàng)建一個正則表達(dá)式
//輸出
[12, 345, 6, A ]

這里正則表達(dá)式語法與Java的完全相同,我們匹配一個點(對它轉(zhuǎn)義表示我們指的時字面量)或者破折號袜茧。
對于一些簡單的情況菜拓,就不需要正則表達(dá)式了,Kotlin中的spilt擴展函數(shù)的其他重載支持任意數(shù)量的純文本字符串分隔符:

println("12.345-6.A".split(".", "-")) //指定多個分隔符

等同于上面正則的分割惫周。

正則表達(dá)式和三重引號的字符串

現(xiàn)在有這樣一個需求:解析文件的完整路徑名稱/Users/liuboyu/kotlin/chapter.adoc到對應(yīng)的組件:目錄尘惧、文件名、擴展名递递。Kotlin標(biāo)準(zhǔn)庫中包含了一些可以用來獲取在給定分隔符第一次(或最后一次)出現(xiàn)之前(或之后)的子字符串的函數(shù)喷橙。

val path = "/Users/liuboyu/kotlin/chapter.adoc"
val directory = path.substringBeforeLast("/")
val fullName = path.substringAfterLast("/")
val fileName = fullName.substringBeforeLast(".")
val extension = fullName.substringAfterLast(".")
println("Dir: $directory, name: $fileName, ext: $extension")

//輸出
Dir: /Users/liuboyu/kotlin, name: chapter, ext: adoc

解析字符串在Kotlin中變得更加容易,但如果你仍然想使用正則表達(dá)式登舞,也是沒有問題的:

val regex = """(.+)/(.+)\.(.+)""".toRegex()
val matchResult = regex.matchEntire(path)
if (matchResult != null) {
    val (directory, fileName, extension) = matchResult.destructured
    println("Dir: $directory, name: $fileName, ext: $extension")
}

這里正則表達(dá)式寫在一個三重引號的字符串中贰逾。在這樣的字符串中,不需要對任何字符進(jìn)行轉(zhuǎn)義菠秒,包括反斜線疙剑,所以可以用\. 而不是\\. 來表示點,正如寫一個普通字符串的字面值践叠。在這個正則表達(dá)式中:第一段(.+) 表示目錄言缤,/ 表示最后一個斜線,第二段(.+)表示文件名禁灼,\.表示最后一個點管挟,第三段(.+) 表示擴展名。

多行三重引號的字符串

三重引號字符串的目的弄捕,不僅在于避免轉(zhuǎn)義字符僻孝,而且使它可以包含任何字符,包括換行符守谓。它提供了一種更簡單的方法穿铆,從而可以簡單的把包含換行符的文本嵌入到程序中:

val kotlinLogo = """|//
        .|//
        .|/ \
    """.trimMargin(".")
print(kotlinLogo)

//輸出
|//
|//
|/ \

多行字符串包含三重引號之間的所有字符,包括用于格式化代碼的縮進(jìn)斋荞。如果要更好的表示這樣的字符串荞雏,可以去掉縮進(jìn)(左邊距)。為此譬猫,可以向字符串內(nèi)容添加前綴讯檐,標(biāo)記邊距的結(jié)尾,然后調(diào)用trimMargin 來刪除每行中的前綴和前面的空格染服。在這個例子中使用了.來作為前綴别洪。

讓你的代碼更整潔:局部函數(shù)和擴展

許多開發(fā)人員認(rèn)為,好代碼的重要標(biāo)準(zhǔn)之一就是減少重復(fù)代碼柳刮。Kotlin提供了局部函數(shù)來解決常見的代碼重復(fù)問題挖垛。下面的例子中是在將user的信息保存到數(shù)據(jù)庫前痒钝,對數(shù)據(jù)進(jìn)行校驗的代碼:

class User(val id: Int, val name: String, val address: String)

fun saveUser(user: User) {
    if (user.name.isEmpty()) {
        throw IllegalArgumentException("Can't save user ${user.id}:empty Name")
    }
    if (user.address.isEmpty()) {
        throw IllegalArgumentException("Can't save user ${user.id}:empty Name")
    }
    //保存user到數(shù)據(jù)庫
}

分別對每個屬性校驗的代碼就是重復(fù)的代碼,特別當(dāng)屬性多的時候就重復(fù)的更多痢毒。這種時候?qū)Ⅱ炞C的代碼放到局部函數(shù)中送矩,可以擺脫重復(fù)同時保持清晰的代碼結(jié)構(gòu)。局部函數(shù)哪替,顧名思義就是定義在函數(shù)中的函數(shù)栋荸。我們使用局部函數(shù)來改造上面這個例子:

class User(val id: Int, val name: String, val address: String)

fun saveUser(user: User) {
    //聲明一個局部函數(shù)
    fun validate(value: String, fieldName: String) {
        if (value.isEmpty()) {
            //局部函數(shù)可以直接訪問外部函數(shù)的參數(shù):user
            throw IllegalArgumentException("Can't save user ${user.id}:empty $fieldName")
        }
    }
    validate(user.name,"Name")
    validate(user.address,"Address")
    //保存user到數(shù)據(jù)庫
}

我們還可以繼續(xù)改進(jìn),將邏輯提取到擴展函數(shù)中:

class User(val id: Int, val name: String, val address: String)

fun User.validateBeforeSave() {
    fun validate(value: String, fieldName: String) {
        if (value.isEmpty()) {
            throw IllegalArgumentException("Can't save user $id:empty $fieldName")
        }
    }
    validate(name, "Name")//擴展函數(shù)直接訪問接收者對象user的屬性
    validate(address, "Address")
}

fun saveUser(user: User) {
    user.validateBeforeSave()
    //保存user到數(shù)據(jù)庫
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末凭舶,一起剝皮案震驚了整個濱河市晌块,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌帅霜,老刑警劉巖匆背,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異身冀,居然都是意外死亡钝尸,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進(jìn)店門搂根,熙熙樓的掌柜王于貴愁眉苦臉地迎上來珍促,“玉大人,你說我怎么就攤上這事剩愧√咝牵” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵隙咸,是天一觀的道長。 經(jīng)常有香客問我成洗,道長五督,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任瓶殃,我火速辦了婚禮充包,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘遥椿。我一直安慰自己基矮,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布冠场。 她就那樣靜靜地躺著家浇,像睡著了一般。 火紅的嫁衣襯著肌膚如雪碴裙。 梳的紋絲不亂的頭發(fā)上钢悲,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天点额,我揣著相機與錄音,去河邊找鬼莺琳。 笑死还棱,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的惭等。 我是一名探鬼主播珍手,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼辞做!你這毒婦竟也來了琳要?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤凭豪,失蹤者是張志新(化名)和其女友劉穎焙蹭,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嫂伞,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡孔厉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了帖努。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片撰豺。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖拼余,靈堂內(nèi)的尸體忽然破棺而出污桦,到底是詐尸還是另有隱情,我是刑警寧澤匙监,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布凡橱,位于F島的核電站,受9級特大地震影響亭姥,放射性物質(zhì)發(fā)生泄漏稼钩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一达罗、第九天 我趴在偏房一處隱蔽的房頂上張望坝撑。 院中可真熱鬧,春花似錦粮揉、人聲如沸巡李。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽侨拦。三九已至,卻和暖如春辐宾,著一層夾襖步出監(jiān)牢的瞬間阳谍,已是汗流浹背蛀柴。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留矫夯,地道東北人鸽疾。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像训貌,于是被迫代替她去往敵國和親制肮。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容