泛型擴展函式
在不變動原本的類別狀況下,寫擴展函式。
String擴展函式
語法
類別.擴展函式名() : 傳回值類型 {
	內容
}
String.addExt() : 傳回值類型 {
	內容
}
this
以下程式碼,this代表呼叫擴展函式的呼叫者。
呼叫者.擴展函式()
"Hello".addExt()
「Hello」就是擴展函式的呼叫者,擴展函式都會自帶this指向擴展函式的呼叫者。
1
2
3
4
5
6
7
fun String.addExt(): String {
    return this + ">>>>>>>>>>"
}
fun main() {
    println("Hello".addExt())
}
Hello>>>>>>>>>>
鏈式呼叫
另外寫了String.myprint()擴展函式。
1
2
3
4
5
6
7
8
9
10
11
fun String.addExt(): String {
    return this + ">>>>>>>>>>"
}
fun String.myprint(): Unit {
    println(this.toString() + "@@@@@")
}
fun main() {
    "Hello".addExt().myprint()
}
Hello>>>>>>>>>>@@@@@
泛型擴展函式
任何類型都可以呼叫myprint(),要如何做?使用泛型擴展函式。
fun <T> T.擴展函式()
以下改寫成泛型擴展函式,任意類型都可以使用myprint()。
1
2
3
4
5
6
7
8
9
fun <T> T.myprint(): Unit {
    println(this.toString() + "@@@@@")
}
fun main() {
    123.myprint()
    true.myprint()
    "ABC".myprint()
}
123@@@@@
true@@@@@
ABC@@@@@
T.let擴展函式
inline是內嵌函式,把函式包在呼叫它的函式程式碼中。
T.let()任何類型都有let()擴展函式。
1
2
3
public inline fun <T, R> T.let(block: (T) -> R): R {
    return block(this)
}
傳入參數給Lambda
以下程式碼block是參數名,參數類型為Lambda匿名函式,傳入(T)任意類型參數,傳回值是R任意類型。
1
2
3
public inline fun <T, R> T.let(block: (T) -> R): R {
    return block(this)
}
block是一個Lambda匿名函式,函式傳入參數x類型為String,傳回類型為Unit。
1
2
3
4
fun main() {
    val block: (String) -> Unit = { x -> print(x) }
    block("Hello world")
}
Hello world
Lambda只有一個參數,可省略x參數,使用it代替x,程式結果與上面執行相同。
1
2
3
4
fun main() {
    val block: (String) -> Unit = { print(it) }
    block("Hello world")
}
Hello world
T.let lambda
1
2
3
public inline fun <T, R> T.let(block: (T) -> R): R {
    return block(this)
}
以下程式碼,花括號{}就是Lambda函式,it就是上面程式碼的this,this是擴展函式呼叫者,也就是"Hello"。
1
2
3
"Hello".let {
    println(it)
}
Hello world
T.let() 傳回值類型
- 建立一個rtn變數接收Lambda傳回值。
- 再將Lambda傳回值,作為傳回值傳回去。
1
2
3
4
5
6
7
8
9
10
11
12
public inline fun <T, R> T.mylet(block: (T) -> R): R {
    // 1.建立一個rtn變數接收Lambda傳回值
    val rtn: R = block(this)
    // 2. 再將Lambda傳回值,作為傳回值傳回去。
    return rtn
}
fun main() {
    val rtn = "Hello World".mylet { println(it) }
    // 印出傳回值類型
    println(rtn)
}
Hello World
kotlin.Unit
T.apply()
T.()
apply()的Lambda參數類型為T.() - > Unit,其中的T.()是什麼?
1
2
3
4
public inline fun <T> T.apply(block: T.() -> Unit): T {
    block()
    return this
}
先前提過,String.擴展函式(),以下的this是擴展函式呼叫者,"Hello world"。
1
2
3
4
5
6
7
fun String.addExt(): String {
    return this + ">>>>>>>>>>"
}
fun main() {
    val str = "Hello world".addExt()
    println(str)
}
Hello world>>>>>>>>>>
所以這邊的T.(),可以視作T.擴展函式(),而這個T.就是呼叫apply()函式的呼叫者,會把呼叫者變成this傳入到T.擴展函式()中。
而下面程式碼的this就是"Hello world",這個字串。
1
2
3
4
5
fun main() {
    "Hello world".apply { 
        println(this + "==========>>>>>>")
    }
}
Hello world==========>>>>>>
this可以隱藏
下面程式碼this就是擴展函式呼叫者File。
1
2
3
4
5
6
fun main() {
    File("/Users/cici/testc/file_test").apply {
        this.setWritable(true)
        this.setExecutable(true) //最後一行的結果才是傳回值
    }
}
也可以把this隱藏起來。
1
2
3
4
5
6
fun main() {
    File("/Users/cici/testc/file_test").apply {
        setWritable(true)
        setExecutable(true) //最後一行的結果才是傳回值
    }
}
T.apply() 傳回值類型
程式碼最後一行傳回的是this,也就是擴展函式呼叫者自己。
1
2
3
4
public inline fun <T> T.apply(block: T.() -> Unit): T {
    block()
    return this
}
還原非泛型的程式碼
把上面程式碼的
1
2
3
4
fun File.apply(block: File.() -> Unit): File {
    block()
    return this
}
to infix
以下是建立一個map。
1
2
3
4
5
6
val map1 = mapOf(
    "Alice" to 18,
    "Alex" to 20,
    "Momo" to 5,
    "Yoyo" to 8
)
to的擴展函式,使用到infix,infix可以把呼叫的點.變成空白。
to的擴展函式傳回值為Pair<A,B>,A與B是泛型類型。
1
public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
原本to的擴展函式程式碼:
1
2
3
fun main() {
    val pair1 = "Alice".to(18)
}
infix可以把點.變空白,跟上面的程式碼是相同的。
1
2
3
fun main() {
    val pair1 = "Alice" to 18
}
Int.until
Int.until是Int的擴展函式,fun前面有infix,代表可以使用空白呼叫。
this是呼叫擴展函式的呼叫者,2.until(7),2就是呼叫者,2就是this。
to是參數,7就是to。
to是參數,傳回值類型是IntRange..。
傳回值 2 .. 7-1
1
2
3
4
public infix fun Int.until(to: Int): IntRange {
    if (to <= Int.MIN_VALUE) return IntRange.EMPTY
    return this .. (to - 1).toInt()
}
原本是
1
2.until(7)
infix可以把點.變空白
1
2 until 7
String.count()擴展函式
String.count()擴展函式是計算字串有幾個字元。
1
println("字元個數 = ${"Hello World".count()}")
字元個數 = 11
count(Lambda參數)
使用有參數的count()擴展函式,參數為Lambda。
1
2
3
4
5
6
7
8
9
public inline fun CharSequence.count(predicate: (Char) -> Boolean): Int {
    var count = 0
    // 遍歷字串每一個字元
    for (element in this) {
      // 如果Lambda傳回值是true,count計數器 + 1
      if (predicate(element)) ++count
    }
    return count
}
Lambda類型為參數是Char,傳回值是Boolean
1
predicate: (Char) -> Boolean
自己寫一個Lambda,參數是letter,如果letter等於o,就傳回true
1
2
3
{ letter ->
    letter == 'o'
}
把Lambda傳入String.count()擴展函式。
1
2
3
4
var str = "Hello World"
val o_count = str.count ({ letter ->
    letter == 'o'
})
簡化,把圓括號去掉。
1
2
3
4
var str = "Hello World"
val o_count = str.count { letter ->
    letter == 'o'
}
簡化,letter與->箭頭去掉,因為只有一個參數,可以去掉,默認用it代替一個參數。
1
2
3
4
var str = "Hello World"
val o_count = str.count {
    it == 'o'
}
完整程式碼
1
2
3
4
5
6
7
8
fun main() {
    var str = "Hello World"
    val o_count = str.count {
        it == 'o'
    }
    println("字元個數 = ${str.count()}")
    println("o字元個數 = ${o_count}")
}
字元個數 = 11
o字元個數 = 2
擴展屬性
除了擴展函式之外,可以為原有的類別,重新定義擴展屬性。
下面程式碼,mylen是自訂屬性,使用get() = count()取得字串長度。
1
2
3
4
5
6
val String.mylen
    get() = count()
fun main() {
    println("字元個數 = ${"Hello World".mylen}")
}