一.擴展函數
1.定義擴展函數
擴展函數可以在不直接修改類定義的情況下增加類功能,擴展函數可以用于自定義類,也可以用于比如List、String,以及Kotlin標準庫里的其他類。和繼承相似,擴展函數也能共享類行為,在你無法接觸某個類定義或者某個類沒有使用open修飾符,導致你無法繼承它時,擴展函數就是增加類功能的最好選擇。
定義擴展函數和定義一般函數差不多,但有一點不大一樣,除了函數定義,你還需要指定接收功能擴展的的接收者類型。
運行結果:
abc!!!!!!!!!!
test
15
在Kotlin文件中定義的擴展函數全局都可以用,如果不想暴露給全局使用,可以使用private修飾
2.泛型擴展函數
如果想在調用addExt擴展函數之前和之后分別打印字符串怎么辦?
直接將easyPrint擴展函數新增Any返回值,在main方法中調用會報錯,因為addExt是String的擴展函數,Any是String的父類,所以easyPrint方法返回必須是String才能調用,如下:
執行結果:
abc
abc!!!!!!!!!!
除了上邊的方法外,還有別的方法,那就是泛型擴展函數
新的泛型擴展函數不僅可以支持任何類型的接收者,還保留了接收者的類型信息,使用泛型類型后,擴展函數能夠支持更多類型的接收者,適用范圍更廣了。
泛型擴展函數在Kotlin標準庫里隨處可見,例如let函數,let函數被定義成了泛型擴展函數,所以能支持任何類型,它接收了一個lambda表達式,這個lambda表達式的接收者T作為值參,返回的R-lambda表達式返回的任何新類型。
二.擴展屬性
1.擴展屬性
除了給類添加功能擴展函數外,你還可以給類定義擴展屬性,給String類添加一個擴展屬性,這個擴展屬性可以統計字符串里有多少個元音字母。
2.可空類擴展
你也可以定義擴展函數用于可空類型,在可空類型上定義擴展函數,你就可以直接在擴展函數內部解決可能出現的空值問題。
運行結果:abc
3.infix關鍵字
上個例子中用到了infix關鍵字,該關鍵字適用于有單個參數的擴展函數和類函數,可以讓你以更簡潔的語法調用函數,如果一個函數定義使用了infix關鍵字,那么調用它時,調用者和函數之間的點以及參數的一對括號都可以不要。
4.定義擴展文件
擴展函數需要在多個文件里面使用,可以將它定義在單獨的文件中,然后import。
定義擴展函數:
使用擴展函數:
5.重命名擴展
有時候,你想使用一個擴展或一個類,但他的名字不合你意。
6.Kotlin標準庫中的擴展
Kotlin標準庫提供的很多功能都是通過擴展函數和擴展屬性來實現的,包含類擴展的標準庫文件通常都是以類名加S后綴來命名的,例如:Sequences.kt,Ranges.kt,Maps.kt
三.DSL
1.帶接收者(接收者就是函數的調用者)的函數字面量
apply函數是如何做到支持接收者對象的隱式調用的。
首先,擴展函數里自帶類調用者的this的隱式調用,apply函數的入參是block: T.() -> Unit,這是一個匿名函數,返回值類型為Unit(無返回值類型),而T.()是什么鬼?我們上邊有介紹泛型擴展函數,比如fun <T> T.easyPrint(): Unit,所以T.() -> Unit就是匿名的泛型擴展函數,這樣做就是為了讓所有類型都可以使用apply這個函數,而且在lambda表達式中還持有調用類的this,這樣就可以在lambda表達式中直接調用該類的方法
2.DSL
使用這樣的編程范式,就可以寫出業界知名的“領域特定語言”(DSL),一種API編程范式,暴露調用者的函數和特性,以便于使用你定義的lambda表達式來讀取和配置它們。
四.函數式編程
一個函數式應用通常由三大類函數構成:變換transform、過濾filter、合并combine。每類函數都針對集合數據類型設計,目標是產生一個最終結果。函數式編程用到的函數生來都是可組合的,也就是說,你可以組合多個簡單函數來構建復雜的計算行為。
1.變換
變換是函數式編程的第一大類函數,變換函數會遍歷集合內容,用一個以值參形式傳入的變換器函數,變換每一個元素,然后返回包含已修改的集合給鏈上的其他函數。
最常用的兩個變換函數是map和flatMap。
2.map
map變換函數會遍歷接收者集合,讓變換器函數作用于集合里的各個元素,返回結果是包含已修改元素的集合,會作為鏈上下一個函數的輸入。
map函數定義如下:
public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R>
運行結果:
[zebra, giraffe, elephant, rat]
[A baby zebra, with the cutest little tail ever!, A baby giraffe, with the cutest little tail ever!, A baby elephant, with the cutest little tail ever!, A baby rat, with the cutest little tail ever!]
可以看到,原始集合沒有被修改,map變換函數和你定義的變換器函數做完事情后,返回的是一個新集合,這樣,變量就不用變來變去了。
事實上,函數式編程范式支持的設計理念就是不可變數據的副本在鏈上的函數間傳遞。
map返回的集合中的元素個數和輸入集合必須一樣,不過,返回的新集合里的元素可以是不同類型的。
運行結果:
[zebra, giraffe, elephant, rat]
[5, 7, 8, 3]
3.flatMap
flatMap函數操作一個集合的集合,將其中多個集合中的元素合并后返回一個包含所有元素的單一集合。
運行結果:
[1, 2, 3, 4, 5, 6]
4.過濾
過濾是函數式編程的第二大類函數,過濾函數接受一個predicate函數,用它按給定條件檢查接收者集合里的元素并給出true或false的判定。如果predicate函數返回true,受檢元素就會添加到過濾函數返回的新集合里。如果predicate函數返回false,那么受檢元素就被移出新集合。
5.filter
filter函數定義如下:
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T>
例1:過濾集合中元素含有“J”字母的元素。
運行結果:
[Jack, Jimmy]
例2:filter過濾函數接收一個predicate函數,在flatMap遍歷他的輸入集合中的所有元素時,filter函數會讓predicate函數按過濾條件,將符合條件的元素都放入它返回的新集合里。最后,flatMap會把變換器函數返回的子集合合并在一個新集合里。
運行結果:
[red apple, red fish]
例3:找素數,除了1和它本身,不能被任何數整除的數。僅使用了幾個簡單函數,我們就解決了找素數這個比較復雜的問題,這就是函數式編程的獨特魅力:每個函數做一點,組合起來就能干大事。
6.合并
合并是函數式編程的第三大類函數,合并函數能將不同的集合合并成一個新集合,這和調用者是包含集合的集合的flatMap函數不同。
7.zip
zip合并函數來合并兩個集合,返回一個包含鍵值對的新集合。
運行結果:
{Jack=large, Jason=x-large, Tommy=medium}
8.fold
另一個可以用來合并值的合并類函數是fold,這個合并函數接收一個初始累加器值,隨后會根據匿名函數的結果更新。
運行結果:30
9.序列
List、Set、Map集合類型,這幾個集合類型統稱為及早集合(eager collection),這些集合的任何一個實例在創建后,他要包含的元素都會被加入并允許你訪問。對應及早集合,Kotlin還有另外一類集合:惰性集合(lazy collection),類似于類的惰性初始化,惰性集合類型的性能表現優異,尤其是用于包含大量元素的集合時,因為集合元素是按需產生的。
Kotlin有個內置惰性集合類型叫序列(Sequence),序列不會索引排序它的內容,也不記錄元素數目,事實上,在使用一個序列時,序列里的值可能有無限多,因為某個數據源能產生無限多個元素。
10.generateSequence
針對某個序列,你可能會定義一個只要序列有新值產生就被調用一下的函數,這樣的函數叫迭代器函數,要定義一個序列和它的迭代器,你可以使用Kotlin的序列構造函數generateSequence,generateSequence函數接收一個初始值作為序列的起步值,在用generateSequence定義的序列上調用一個函數時,generateSequence函數會調用你置頂的迭代器函數,決定下一個要產生的值。
惰性集合究竟有什么用呢?為什么要用它而不是List集合呢?假設你想產生頭1000個素數。
運行結果:669
這樣的代碼實現表明,你不知道該檢查多少個數才能得到整1000個素數,所以用5000這個預估數。但事實上5000個數遠遠不夠,只能找出669個素數。
使用如上這種方式(generateSequence)就可以得到1000個素數了,運行結果:1000