Kotlin細節(jié)文章筆記整理更新進度:
Kotlin系列 - 基礎(chǔ)類型結(jié)構(gòu)細節(jié)小結(jié)(一)
Kotlin系列 - 函數(shù)與類相關(guān)細節(jié)小結(jié)(二)
1.高階函數(shù)
基本概念: 傳入或者返回函數(shù)的函數(shù)
函數(shù)引用:引用的函數(shù)名前加上 ::
- 有以下幾種類型:
- 類成員方法引用:
類名::成員方法名
- 擴展函數(shù)引用:
類名::擴展函數(shù)名
- 實例函數(shù)引用:
實例名::成員方法名
- 包級別函數(shù)引用:
::函數(shù)名
第一個例子:
打印數(shù)組中的元素(傳入包級別函數(shù))
fun main(args:Array<String>) {
args.forEach(::println) //函數(shù)引用
}
public actual inline fun println(message: Any?) {
System.out.println(message)
}
public inline fun <T> Array<out T>.forEach(action: (T) -> Unit): Unit {
for (element in this) action(element)
}
forEach(action: (T) -> Unit)
:要求傳入了一個函數(shù)廉涕,參數(shù)為(action: (T) -> Unit)
,類型為一個參數(shù)T
,返回值為Unit
println(message: Any?)
:類型為一個參數(shù)T
,返回值為Unit
我們調(diào)用args.forEach(::println)
將println
函數(shù)傳入給forEach
第二個例子:
過濾數(shù)組中的空字符串(傳入類成員函數(shù))
fun main(args:Array<String>) {
args.filter(String::isNotEmpty)
}
public inline fun <T> Array<out T>.filter(predicate: (T) -> Boolean): List<T> {
return filterTo(ArrayList<T>(), predicate)
}
public inline fun CharSequence.isNotEmpty(): Boolean = length > 0
這里有個點要注意下: filter
要求傳入的函數(shù)類型為(predicate: (T) -> Boolean)
,但是我們傳入的String::isNotEmpty
這個方法并沒有參數(shù)0妗X舶琛具则!public inline fun CharSequence.isNotEmpty(): Boolean = length > 0
只有一個返回值Boolean
為什么可以呢苛骨??顾稀?
答案:因為
類名::成員方法名
默認就有一個參數(shù)达罗,這個函數(shù)類型就是類名這個類型的
。比如上面的String::isNotEmpty
相當于isNotEmpty(String)
第三個例子:
打印數(shù)組中的元素(傳入實例函數(shù))
fun main(args:Array<String>) {
val t = Test()
args.forEach(t::testName)
}
class Test{
fun testName(name:String){
println(name)
}
}
public inline fun <T> Array<out T>.forEach(action: (T) -> Unit): Unit {
for (element in this) action(element)
}
這里傳入的是t::testName
,實例名::成員方法名
就不會默認多出一個參數(shù)静秆。如果使用Test::testName
會顯示報錯信息粮揉,也驗證了我們上面說的類名::成員方法名
默認就有一個參數(shù)。
這里總結(jié)一下:函數(shù)引用抚笔,就是將函數(shù)作為參數(shù)變量傳入具體某個方法中扶认,也可以賦值給變量。注意的是殊橙,如果是類成員函數(shù)辐宾、擴展函數(shù)引用(
類名:函數(shù)名
),默認參數(shù)會多一個就是類本身這個參數(shù)
2. 閉包
- 函數(shù)運行的環(huán)境
- 持有函數(shù)運行狀態(tài)
- 函數(shù)內(nèi)部可以定義函數(shù)/類
fun add(x: Int): (Int) -> Int {
return fun(y: Int): Int {
return x + y
}
}
fun main() {
var add2 = add(2)
println(add2(10))
}
函數(shù)的定義方法可以傳入函數(shù)蛀柴,也可以返回函數(shù)螃概,函數(shù)內(nèi)的作用域包含了函數(shù)內(nèi)的子函數(shù)跟子類等。
格式 :fun 方法名(形參:函數(shù)類型) 函數(shù)類型{}
函數(shù)類型基本寫法:
() -> Unit
(多個參數(shù)) -> 返回類型
3. 函數(shù)復(fù)合
- f(g(x)) 函數(shù)傳入函數(shù)
//定義兩個函數(shù)
val add5 = { i: Int -> i + 5 }
val multiplyBy2 = { i: Int -> i * 2 }
fun main() {
println(multiplyBy2(add5(9)))
}
-----打印出來的Log
28
上面是基本的展示鸽疾,函數(shù)中傳入函數(shù)吊洼。
下面擴展一下函數(shù):
//定義三個函數(shù)
val add5 = { i: Int -> i + 5 }
val multiplyBy2 = { i: Int -> i * 2 }
val sum = { q: Int, w: Int -> q + w }
//關(guān)鍵點1:
infix fun <P1, P2, R> Function1<P1, P2>.andThen(function: Function1<P2, R>): Function1<P1, R> {
return fun(p1: P1): R {
return function.invoke(this.invoke(p1))
}
}
// 關(guān)鍵點2:
infix fun <P1,P2,R> Function1<P2,R>.compose(function:Function1<P1,P2>):Function1<P1,R>{
return fun (p1:P1):R{
return this.invoke(function.invoke(p1))
}
}
//關(guān)鍵點3:
fun <P1, P2, P3, R> Function2<P2, P3, R>.toAllSum(
function: Function1<P1, P2>,
function1: Function1<P2, P3>
): Function2<P1, P2, R> {
return fun(p1: P1, p2: P2): R {
return this.invoke(function.invoke(p1), function1.invoke(p2))
}
}
fun main() {
// 關(guān)鍵點3:
val add5AndMulti2 = add5 andThen multiplyBy2
// 關(guān)鍵點4:
val add5ComposeMulti2 = add5 compose multiplyBy2
//關(guān)鍵點5:
val sum = sum.toAllSum(add5, multiplyBy2)
println(add5AndMulti2(10))
println(add5ComposeMulti2(10))
println(sum(10,10))
}
-----打印出來的Log
30
25
35
上面實際上就是擴展函數(shù),然后在函數(shù)中傳入函數(shù)跟返回函數(shù)制肮,只有一個參數(shù)的則使用了
infix
中綴關(guān)鍵字冒窍。關(guān)鍵點1、2豺鼻、3都是擴展了函數(shù)類型综液,其中關(guān)鍵點1跟2 擴展函數(shù)類型為傳入一個函數(shù)參數(shù),關(guān)鍵點3擴展函數(shù)傳入兩個函數(shù)參數(shù)
舉例:關(guān)鍵點1:函數(shù)類型為
Function<P1,P2>
擴展函數(shù)andThen
,傳入函數(shù)類型Function1<P2, R>
儒飒,返回函數(shù)類型Function1<P1, R>
第一個return
:返回函數(shù)類型為fun(p1: P1): R
第二個return
:function.invoke(this.invoke(p1))
谬莹,先是傳入this.invoke(p1)
再將這里返回的值傳入
function.invoke()
.
先調(diào)用了了andThen
前的函數(shù),再調(diào)用andThen
后面的函數(shù)。
大家可以根據(jù)這些寫法自定義多種擴展函數(shù)~~
4. run附帽、let埠戳、with、apply蕉扮、also等語法糖部分解析
- 先以
run
為例子
//方法一
public inline fun <R> run(block: () -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
//方法二
public inline fun <T, R> T.run(block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
方法一函數(shù)簽名:run(block: () -> R): R
直接傳入代碼塊整胃,并返回R
方法二函數(shù)簽名:T.run(block: T.() -> R): R
,傳入的代碼塊block: T.() -> R
喳钟,也就是調(diào)用者本身的引用屁使,則在block
中則直接可以使用T
中的成員變量及函數(shù)等
//使用
var sum = run { 5+3 }
println(sum)
var aList = arrayListOf("小明", "小紅", "小黑")
var aListSize = aList.run { size }
println(aListSize)
--------------------打印出來的
8
3
這個
contract {...}
看不懂可以暫時不用管它,kotlin
中契約的一種寫法,詳情可以看一下https://kotlinlang.org/docs/reference/whatsnew13.html#contracts
-
with
奔则、apply
蛮寂、also
、let
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return receiver.block()
}
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
public inline fun <T> T.also(block: (T) -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block(this)
return this
}
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
-
with
:接受兩個參數(shù)应狱,一個是自己本身共郭,一個block: T.() -> R
,返回return receiver.block()
;
with
用法:
var aList = arrayListOf("小明", "小紅", "小黑")
var l = with(aList){
add("小黃")
removeAt(0)
forEach {
print("$it疾呻、")
}
size
}
println(l)
---------------------打印
小紅除嘹、小黑、小黃岸蜗、3
-
apply
:方法的擴展函數(shù)尉咕,傳入block: T.() -> Unit
,返回調(diào)用者本身璃岳,用法與run
一致年缎,但是最后返回的是調(diào)用者本身。 -
also
:方法的擴展函數(shù)铃慷,傳入block: (T) -> Unit
单芜,這里更前面幾個方法有點不一樣,block
傳入了T
這個調(diào)用者本身犁柜,并且函數(shù)最后返回調(diào)用者本身洲鸠。
also
用法:
var aList = arrayListOf("小明", "小紅", "小黑")
val sizeFinally = aList.also {
println(it.size)
it.add("小黃")
it.add("小綠")
}.size
println(sizeFinally)
---------打印
3
5
-
let
:方法的擴張函數(shù),傳入block: (T) -> R
馋缅,let
方法返回R
扒腕。
let
用法:
val sizeFinally = aList.let {
println(it.size)
it.add("小黃")
it.add("小綠")
it.size
}
println(sizeFinally)
---------------打印
3
5
補充: 尾遞歸優(yōu)化 tailrec
- 將
tailrec
關(guān)鍵字添加到fun
前提示編譯器尾遞歸優(yōu)化。
尾遞歸:是遞歸的一種形式萤悴,遞歸中在調(diào)用完自己后沒有其他操作的稱為尾遞歸瘾腰。 - 尾遞歸與迭代的關(guān)系:尾遞歸可以直接轉(zhuǎn)換成迭代(好吧,其實這個我也不是很清楚~)
//符合尾遞歸 可以加tailrec 關(guān)鍵字優(yōu)化
tailrec fun findListNode(head: ListNode?, value: Int): ListNode? {
head ?: return null
if (head.value == value) return head
return findListNode(head.next, value)
}
// 不符合尾遞歸 因為最后調(diào)用完自己還跟n相乘
fun factorial(n:Long):Long{
return n * factorial(n-1)
}
上面的方法中第一種是符合尾遞歸的形式覆履,這種我們可以加tailrec
關(guān)鍵字蹋盆,有什么好處呢费薄?
fun main() {
var listNode = ListNode(0)
var p =listNode
for (i in 1..100000) {
p.next = ListNode(i)
p = p.next!!
}
println(findListNode(listNode,99998)?.value)
}
-----------有加了關(guān)鍵字tailrec -打印出來的Log
99998
----------沒有加關(guān)鍵字打印出來的Log
Exception in thread "main" java.lang.StackOverflowError
因為加了tailrec
關(guān)鍵字,實際上是優(yōu)化成了迭代相比遞歸減低了內(nèi)存空間的開銷栖雾。