寫在開頭:本人打算開始寫一個Kotlin系列的教程,一是使本身記憶和理解的更加深入,二是能夠分享給一樣想學習Kotlin的同窗。系列文章的知識點會以《Kotlin實戰》這本書中順序編寫,在將書中知識點展現出來同時,我也會添加對應的Java代碼用於對比學習和更好的理解。java
Kotlin教程(一)基礎
Kotlin教程(二)函數
Kotlin教程(三)類、對象和接口
Kotlin教程(四)可空性
Kotlin教程(五)類型
Kotlin教程(六)Lambda編程
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中調用擴展函數和其餘頂層函數同樣,經過.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中就是靜態函數。
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 1
、list 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到數據庫
}
複製代碼