Kotlin教程(二)函數

寫在開頭:本人打算開始寫一個Kotlin系列的教程,一是使本身記憶和理解的更加深入,二是能夠分享給一樣想學習Kotlin的同窗。系列文章的知識點會以《Kotlin實戰》這本書中順序編寫,在將書中知識點展現出來同時,我也會添加對應的Java代碼用於對比學習和更好的理解。java

Kotlin教程(一)基礎
Kotlin教程(二)函數
Kotlin教程(三)類、對象和接口
Kotlin教程(四)可空性
Kotlin教程(五)類型
Kotlin教程(六)Lambda編程
Kotlin教程(七)運算符重載及其餘約定
Kotlin教程(八)高階函數
Kotlin教程(九)泛型正則表達式


在Kotlin中建立集合

上一章咱們已經使用setOf 函數建立一個set了。一樣的,咱們也能夠用相似的方法建立一個list或者map:數據庫

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

to 並非一個特殊的結構,而是一個普通函數,在後面會繼續探討它。 有沒有想過這裏建立出來的set、list、map究竟是什麼類型的那?能夠經過.javaClass 屬性獲取類型,至關於Java中的getClass() 方法:編程

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

//輸出
class java.util.LinkedHashSet
class java.util.Arrays$ArrayList
class java.util.LinkedHashMap
複製代碼

能夠看到都是標準的Java集合類,Kotlin沒有本身專門的集合類,是爲了更容易與Java代碼交互,當從Kotlin中調用Java函數的時候,不用轉換它的集合類來匹配Java的類,反之亦然。 儘管Kotlin的集合類和Java的集合類徹底一致,但Kotlin還不止於此。舉個例子,能夠經過如下方法來獲取一個列表中最後一個元素,或者獲得一個數字列表的最大值:數組

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

//輸出
fourteenth
14
複製代碼

或許你應該知道last()max() 在Java的集合類中並不存在,這應該是Kotlin本身擴展的方法,能夠你要知道上面咱們打印出來的類型明確是Java中的集合類,但在這裏調用方法的對象就是這些集合類,又是怎麼作到讓一個Java中的類調用它自己沒有的方法那?在後面咱們講到擴展函數的時候你就會知道了!bash

讓函數更好調用

如今咱們知道了如何建立一個集合,接下來讓咱們打印它的內容。Java的集合都有一個默認的toString 實現,但它的何世華的輸出是固定的,並且每每不是你須要的樣子:app

val list = listOf(1, 2, 3)
println(list) //觸發toString的調用

//輸出
[1, 2, 3]
複製代碼

假設你須要用分號來分隔每個元素,而後用括號括起來,而不是採用默認實現。要解決這個問題,Java項目會使用第三方庫,好比Guava和Apache Commons,或者是在這個項目中重寫打印函數。在Kotlin中,它的標準庫中有一個專門的函數來處理這種狀況。 可是這裏咱們先不借助Kotlin的工具,而是本身寫實現函數:ide

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(1, 2, 3)
println(joinToString(list, ";", "(", ")"))

//輸出
(1;2;3)
複製代碼

看來是可行的,接下來咱們要考慮的是如何修改讓這個函數的調用更加簡潔呢?畢竟每次調用都要傳入四個參數也是挺麻煩的。工具

命名參數

咱們關注的第一個問題就是函數的可讀性。就以joinToString 來看:

joinToString(list, "", "", "")
複製代碼

你能看得出這些String都對應什麼參數嗎?可能必需要藉助IDE工具或者查看函數說明或者函數自己才能知道這些參數的含義。 在Kotlin中,能夠作的更優雅:

println(joinToString(list, separator = "", prefix = "", postfix = ""))
複製代碼

當你調用一個Kotlin定義的函數時,能夠顯示得標明一些參數的名稱。若是在調用一個函數時,指明瞭一個參數的名稱,爲了不混淆,那它以後的全部參數都須要標明名稱。

ps: 當你在Kotlin中調用Java定義的函數時,不能採用命名參數。由於把參數名稱存到 .class文件是Java8以及更高版本的一個可選功能,而Kotlin須要保持和Java6的兼容性。

可能到這裏你只是以爲命名參數讓函數便於理解,可是調用變得複雜了,我還得多寫參數的名稱!別急,與下面說的默認參數相結合時,你就知道命名參數的好了。

默認參數值

Java的另外一個廣泛存在問題是:一些類的重載函數實在太多了。這些重載大可能是爲了向後兼容,方便API的使用者,最終致使的結果是重複。 在Kotlin中,能夠在聲明函數的時候,指定參數的默認值,這樣能夠避免建立重載的函數。讓咱們嘗試改進一下前面的joinToString 函數。在大多數狀況下,咱們可能只會改變分隔符或者改變先後綴,因此咱們把這些設置爲默認值:

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()
}
複製代碼

如今在調用一下這個函數,能夠省略掉有默認值的參數,效果就像在Java中聲明的重載函數同樣。

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

//輸出
1,2,3
1;2;3
複製代碼

當你使用常規的調用語法時,必須按照函數申明中定義的參數順序來給定參數,能夠省略的只有排在末尾的參數。若是使用命名參數,能夠省略中的一些參數,也能夠以你想要的任意 順序只給定你須要的參數:

//打亂了參數順序,而且separator參數使用了默認值
println(joinToString(prefix = "{", collection = list, postfix = "}"))

//輸出
{1,2,3}
複製代碼

注意,參數的默認值是被編譯到被調用的函數中,而不是調用的地方。若是你改變了參數默認值並從新編譯這個函數,沒有給參數從新賦值的調用者,將會開始使用新的默認值。

Java沒有參數默認值的概念,當你從Java中調用Kotlin函數的時候,必須顯示得指定全部參數值。若是須要從Java代碼中調用也能更簡便,可使用@JvmOverloads註解函數。這個指示編譯器生成Java的重載函數,從最後一個開始省略每一個函數。例如joinToString函數,編譯器就會生成以下重載函數: public static final String joinToString(@NotNull Collection collection, @NotNull String separator, @NotNull String prefix) public static final String joinToString(@NotNull Collection collection, @NotNull String separator) public static final String joinToString(@NotNull Collection collection) 所以,當你項目同時存在Java和Kotlin時,對有默認參數值的函數習慣性地加上@JvmOverloads註解是個不錯的作法。

消除靜態工具類:頂層函數和屬性

Java做爲一門面對對象的語言,須要全部的代碼都寫做類的函數。但實際上項目中總有一些函數不屬於任何一個類,最終產生了一些類不包含任何狀態或者實例函數,僅僅是做爲一堆靜態函數的容器。在JDK中,最明顯的例子應該就是Collections了,還有你的項目中是否是有不少以Util做爲後綴的類? 在Kotlin中,根本不須要去建立這些無心義的類,你能夠把這些函數直接放到代碼文件的頂層,不用從屬於任何類。事實上joinToString函數以前就是直接定義在Join.kt 文件。

package com.huburt.imagepicker

@JvmOverloads
fun <T> joinToString(...): String {...}
複製代碼

這會怎樣運行呢?當編譯這個文件的時候,會生成一些類,由於JVM只能執行類中的代碼。當你在使用Kotlin的時候,知道這些就夠了。可是若是你須要從Java中來調用這些函數,你就必須理解它將怎樣被編譯,來看下編譯後的類是怎樣的:

package com.huburt.imagepicker

public class JoinKt {
	public static String joinToString(...){...}
}
複製代碼

能夠看到Kotlin編譯生成的類的名稱,對應於包含函數的文件名稱,這個文件中的全部頂層函數編譯爲這個類的靜態函數。所以,當從Java調用這個函數的時候,和調用任何其餘靜態函數同樣簡單:

import com.huburt.imagepicker.JoinKt

JoinKt.joinToString(...)
複製代碼

修改文件類名

是否是以爲Kt結尾的類使用起來很彆扭,Kotlin提供了方法改變生成類的類名,只須要爲這個kt文件添加@JvmName的註解,將其放到這個文件的開頭,位於包名的前面:

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

@JvmOverloads
fun <T> joinToString(...): String {...}
複製代碼

如今就能夠用新的類名調用這個函數:

import com.huburt.imagepicker.Join

Join.joinToString(...)
複製代碼

頂層屬性

和函數同樣,屬性也能夠放到文件的頂層。從Java的角度來看就是靜態屬性,沒啥特別的,並且因爲沒有了類的存在,這種屬性用到的機會也很少。 須要注意的是頂層函數和其餘任意屬性同樣,默認是經過訪問器暴露給Java使用的(也就是經過getter和setter方法)。爲了方便使用,若是你想要把一個常量以public static final 的屬性暴露給Java,能夠用const 來修飾屬性:

const val TAG = "tag"
複製代碼

這樣就等同與Java的:

public static final String TAG = "tag"
複製代碼

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

Kotlin的一大特點就是能夠平滑的與現有代碼集成。你能夠徹底在原有的Java代碼基礎上開始使用Kotlin。對於原有的Java代碼能夠不修改源碼的狀況下擴展功能:擴展函數。這一點是我認爲Kotlin最強大的地方了。 擴展函數很是簡單,它就是一個類的成員函數,不過定義在類的外面。爲了方便闡述,讓咱們添加一個方法,來計算一個字符串的最後一個字符:

package strings

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

你所要作的,就是把你要擴展的類或者接口的名稱,放到即將添加的函數前面,這個類的名稱被稱爲接收者類型;用來調用這個擴展函數的那個對象,叫作接收者對象。 接着就能夠像調用類的普通成員函數同樣去調用這個函數了:

println("Kotlin".lastChar())
//輸出
n
複製代碼

在這個例子中,String就是接收者類型,而「Kotlin」就是接收者對象。 在這個擴展函數中,能夠像其餘成員函數同樣用this,也能夠像普通函數同樣省略它:

package strings

fun String.lastChar(): Char = get(length - 1) //省略this調用string對象其餘函數
複製代碼

導入擴展函數

對於你定義的擴展函數,它不會自動的在整個項目範圍內生效。若是你須要使用它,須要進行導入,導入單個函數與導入類的語法相同:

import strings.lastChar

val c = "Kotlin".lastChar()
複製代碼

固然也能夠用*表示文件下全部內容:import strings.*

另外還可使用as 關鍵字來修改導入的類或則函數的名稱:

import strings.lastChar as last

val c = "Kotlin".last()
複製代碼

在導入的時候重命名能夠解決函數名重複的問題。

從Java中調用擴展函數

實際上,擴展函數是靜態函數,它把調用對象做爲函數的第一個參數。在Java中調用擴展函數和其餘頂層函數同樣,經過.kt文件生成Java類調用靜態的擴展函數,把接收者對象傳入第一個參數便可。例如上面提到的lastChar擴展函數是定義在StringUtil.kt中,在Java中就能夠這樣調用:

char c = StringUtilKt.lastChar("Java")
複製代碼

做爲擴展函數的工具函數

如今咱們能夠寫一個joinToString 函數的終極版本了,它和你在Kotlin標準庫中看到的如出一轍:

@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 = "}"))
複製代碼

將原來的參數Collection,提出來,做爲接收者類型編寫的擴展函數,使用方法也像是Collection類的成員函數同樣了(固然Java調用仍是靜態方法,第一個參數傳入Collection對象)。

不可重寫的擴展函數

先來看一個重寫的例子:

//Kotlin中class默認是final的,若是須要繼承須要修飾open,函數也相同
open class View {
    open fun click() = println("View clicked")
}

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

當你聲明瞭類型爲View的變量,那它能夠被賦值爲Button類型的對象,由於Button是View的一個子類。當你在調用這個變量的通常函數,好比click的時候,若是Button複寫了這個函數,name這裏將會調用到Button中複寫的函數:

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

//輸出
Button clicked
複製代碼

可是對於擴展函數來講,並非這樣的。擴展函數並非類的一部分,它是聲明在類以外的。儘管能夠給基類和子類都分別定義一個同名的擴展函數,當這個函數被調用時,它會用到哪個呢?這裏,它是由該變量的靜態類型所決定的,而不是這個變量的運行時類型。

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! 複製代碼

當你在調用一個類型爲View的變量的showOff函數時,對應的擴展函數會被調用,儘管實際上這個變量如今是一個Button對象。回想一下,擴展函數會在Java中編譯爲靜態函數,同時接受值將會做爲第一個參數。這樣其實2個showOff擴展函數就是不一樣參數的靜態函數,

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

參數的類型決定了調用那個靜態函數,想要調用Button的擴展函數,則必須先將參數轉成Button類型才行:XxKt.showOff((Button)view);

所以,擴展函數也是有侷限性的,擴展函數是能擴展,即定義新的函數,而不能重寫改變原有函數的實現(本質是一個靜態函數)。若是定了一個類中自己存在成員函數同名的擴展函數,Kotlin種調用該方法的時候會如何呢?(Java中沒有這個顧慮,調用方式不一樣)

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

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

val view = View()
view.click()

//輸出
View clicked
複製代碼

明顯了吧~ 對於有同名成員函數和擴展函數時,在Kotlin中調用始終執行成員函數的代碼,擴展函數並不起做用,至關於沒有定義。這一點在實際開發中須要特別注意了!

擴展屬性

擴展屬性提供了一種方法,用於擴展類的API,能夠用來訪問屬性,用的是屬性語法而不是函數的語法。儘管他們被稱爲屬性,但它們能夠沒有任何狀態,由於沒有合適的地方來存儲它,不可能給現有的Java對象的實例添加額外的字段。舉個例子吧:

val String.lastChar: Char
    get() = get(length - 1)
複製代碼

一樣是獲取字符串的最後一個字符,此次是用擴展屬性的方式定義。擴展屬性也像接收者的一個普通成員屬性同樣,這裏必須定義getter函數,由於沒有支持字段,所以沒有默認的getter的實現。同理,初始化也不能夠:由於沒有地方存儲初始值。 剛剛定義是一個val的擴展屬性,也能夠定義var屬性:

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

還記得上一篇文章的自定義訪問器的內容嗎?這裏的定義方式與自定義訪問器一致,val 屬性不可變,所以只須要定義getter,而var 屬性可變,因此getter和setter都須要。 可能不是很好理解擴展屬性,或者會和真正的屬性混淆,下面列出了擴展屬性轉換成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);
   }
複製代碼

和擴展函數是相同的,僅僅是靜態函數:提供獲取lastChar的功能,這樣的定義方式能夠在Kotlin中像使用普通屬性的調用方式來使用擴展屬性,給你一種這是屬性的感受,但本質上在Java中就是靜態函數。

處理集合:可變參數、中綴調用和庫的支持

擴展Java集合的API

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

還記的以前咱們使用上面的方式獲取了list的最後一個元素,以及set中的最大值。到這裏你可能已經知道了,last()max() 都是擴展函數,本身點進方法驗證一下吧!

可變參數

若是你也看了listOf 函數的定義,你必定看到了這個:

public fun <T> listOf(vararg elements: T): List<T>
複製代碼

也就是vararg 關鍵字,這讓函數支持任意個數的參數。在Java中一樣的可變參數是在類型後面跟上... ,上面的方法在Java則是:

public <T> List<T> listOf(T... elements)
複製代碼

可是Kotlin的可變參數相較於Java仍是有點區別:當須要傳遞的參數已經包裝在數組中時,調用該函數的語法,在Java中能夠按原樣傳遞數組,而Kotlin則要求你顯示地解包數組,以便每一個數組元素在函數中能做爲單獨的參數來調用。從技術的角度來說,這個功能被稱爲展開運算符,而使用的時候,不過是在對應的參數前面放一個*

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]
複製代碼

經過對照能夠看到,若是不加* ,實際上是把數組對象當作了集合的元素。加上* 纔是將數組中全部元素添加到集合中。listOf 也能夠指定泛型<String> ,你能夠嘗試在listOf("c", array) 這裏加泛型,第二個參數array就會提示類型不正確。

Java中沒有展開,咱們也能夠調用Kotlin的listOf函數,該函數聲明在Collections.kt文件下:

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

//輸出
[a, b]
複製代碼

Java中能夠直接傳入數組,可是不能同時傳入單個元素和數組。

鍵值對的處理:中綴調用和解構聲明

還記得建立map的方式嗎?

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

以前說過to 並非一個內置的結構,而是一種特殊的函數調用,被稱爲中綴調用。 在中綴調用中,沒有添加額外的分隔符,函數名稱是直接放在目標對象名稱和參數之間的,如下兩種調用方式是等價的:

1.to("one")//普通調用
1 to "one" //中綴調用
複製代碼

中綴調用能夠與只有一個參數的函數一塊兒使用,換句話說就是隻要函數只有一個參數,均可以支持在Kotlin中的中綴調用,不管是普通的函數仍是擴展函數。要容許使用中綴符號調用函數,須要使用infix 修飾符來標記它。例如to 函數的聲明:

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

to函數會返回一個Pair類型的對象,Pair是Kotlin標準庫中的類,它是用來表示一對元素。咱們也能夠直接用Pair的內容來初始化兩個變量:

val (number, name) = 1 to "one"
複製代碼

這個功能稱之爲解構聲明,1 to "one" 會返回一個Pair對象,Pair包含一對元素,也就是1和one,接着又定義了變量(number, name) 分別指向Pair中的1和one。 解構聲明特徵不止用於Pair。還可使用map的key和value內容來初始化兩個變量。而且還適用於循環,正如你在使用的withIndex 函數的joinToString 實現中看到的:

for ((index, element) in collection.withIndex()) {
    println("$index, $element")
}
複製代碼

to 函數是一個擴展函數,能夠建立一對任何元素,這意味着它是泛型接受者的擴展:可使用1 to "one""one" to 1list to list.size()等寫法。咱們來看看mapOf 函數的聲明:

public fun <K, V> mapOf(vararg pairs: Pair<K, V>): Map<K, V>
複製代碼

listOf 同樣,mapOf 接受可變數量的參數,但此次他們應該是鍵值對。儘管在Kotlin中建立map可能看起來像特殊的解構,而它不過是一個具備簡明語法的常規函數。

字符串和正則表達式的處理

Kotlin定義了一系列擴展函數,使標準Java字符串使用起來更加方便。

分割字符串

Java中咱們會使用String的split方法分割字符串。但有時候會產生一些意外的狀況,例如當咱們這樣寫"12.345-6.A".split(".") 的時候,咱們期待的結果是獲得一個[12, 345-6, A]數組。可是Java的split方法居然返回一個空數組!這是應爲它將一個正則表達式做爲參數,並根據表達式將字符串分割成多個字符串。這裏的點(.)是表示任何字符的正則表達式。 在Kotlin中不會出現這種使人費解的狀況,由於正則表達式須要一個Regex類型承載,而不是String。這樣確保了字符串不會被當作正則表達式。

println("12.345-6.A".split("\\.|-".toRegex())) //顯示地建立一個正則表達式
//輸出
[12, 345, 6, A ]
複製代碼

這裏正則表達式語法與Java的徹底相同,咱們匹配一個點(對它轉義表示咱們指的時字面量)或者破折號。 對於一些簡單的狀況,就不須要正則表達式了,Kotlin中的spilt擴展函數的其餘重載支持任意數量的純文本字符串分隔符:

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

等同於上面正則的分割。

正則表達式和三重引號的字符串

如今有這樣一個需求:解析文件的完整路徑名稱/Users/hubert/kotlin/chapter.adoc 到對應的組件:目錄、文件名、擴展名。Kotlin標準庫中包含了一些能夠用來獲取在給定分隔符第一次(或最後一次)出現以前(或以後)的子字符串的函數。

val path = "/Users/hubert/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/hubert/kotlin, name: chapter, ext: adoc
複製代碼

解析字符串在Kotlin中變得更加容易,但若是你仍然想使用正則表達式,也是沒有問題的:

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

這裏正則表達式寫在一個三重引號的字符串中。在這樣的字符串中,不須要對任何字符進行轉義,包括反斜線,因此能夠用\. 而不是\\. 來表示點,正如寫一個普通字符串的字面值。在這個正則表達式中:第一段(.+) 表示目錄,/ 表示最後一個斜線,第二段(.+) 表示文件名,\. 表示最後一個點,第三段(.+) 表示擴展名。

多行三重引號的字符串

三重引號字符串的目的,不只在於避免轉義字符,並且使它能夠包含任何字符,包括換行符。它提供了一種更簡單的方法,從而能夠簡單的把包含換行符的文本嵌入到程序中:

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

//輸出
|//
|//
|/ \
複製代碼

多行字符串包含三重引號之間的全部字符,包括用於格式化代碼的縮進。若是要更好的表示這樣的字符串,能夠去掉縮進(左邊距)。爲此,能夠向字符串內容添加前綴,標記邊距的結尾,而後調用trimMargin 來刪除每行中的前綴和前面的空格。在這個例子中使用了. 來做爲前綴。

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

許多開發人員認爲,好代碼的重要標準之一就是減小重複代碼。Kotlin提供了局部函數來解決常見的代碼重複問題。下面的例子中是在將user的信息保存到數據庫前,對數據進行校驗的代碼:

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到數據庫
}
複製代碼

分別對每一個屬性校驗的代碼就是重複的代碼,特別當屬性多的時候就重複的更多。這種時候將驗證的代碼放到局部函數中,能夠擺脫重複同時保持清晰的代碼結構。局部函數,顧名思義就是定義在函數中的函數。咱們使用局部函數來改造上面這個例子:

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

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

咱們還能夠繼續改進,將邏輯提取到擴展函數中:

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")//擴展函數直接訪問接收者對象user的屬性
    validate(address, "Address")
}

fun saveUser(user: User) {
    user.validateBeforeSave()
    //保存user到數據庫
}
複製代碼
相關文章
相關標籤/搜索