Kotlin細節文章筆記整理更新進度:
Kotlin系列 - 基礎類型結構細節小結(一)
Kotlin系列 - 函數與類相關細節小結(二)html
基本概念: 傳入或者返回函數的函數 函數引用:引用的函數名前加上 ::
java
類名::成員方法名
類名::擴展函數名
實例名::成員方法名
::函數名
第一個例子:數組
打印數組中的元素(傳入包級別函數)bash
fun main(args:Array<String>) {
args.forEach(::println) //函數引用
}
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)
:要求傳入了一個函數,參數爲(action: (T) -> Unit)
,類型爲一個參數T
,返回值爲Unit
println(message: Any?)
:類型爲一個參數T
,返回值爲Unit
咱們調用args.forEach(::println)
將println
函數傳入給forEach
閉包
第二個例子:app
過濾數組中的空字符串(傳入類成員函數)函數
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
要求傳入的函數類型爲(predicate: (T) -> Boolean)
,可是咱們傳入的String::isNotEmpty
這個方法並無參數!!!public inline fun CharSequence.isNotEmpty(): Boolean = length > 0
只有一個返回值Boolean
爲何能夠呢???post
答案:由於
類名::成員方法名
默認就有一個參數,這個函數類型就是類名這個類型的
。好比上面的String::isNotEmpty
至關於isNotEmpty(String)
優化
第三個例子:ui
打印數組中的元素(傳入實例函數)
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
,實例名::成員方法名
就不會默認多出一個參數。若是使用Test::testName
會顯示報錯信息,也驗證了咱們上面說的類名::成員方法名
默認就有一個參數。
這裏總結一下:函數引用,就是將函數做爲參數變量傳入具體某個方法中,也能夠賦值給變量。注意的是,若是是類成員函數、擴展函數引用(
類名:函數名
),默認參數會多一個就是類自己這個參數
fun add(x: Int): (Int) -> Int {
return fun(y: Int): Int {
return x + y
}
}
fun main() {
var add2 = add(2)
println(add2(10))
}
複製代碼
函數的定義方法能夠傳入函數,也能夠返回函數,函數內的做用域包含了函數內的子函數跟子類等。 格式 :
fun 方法名(形參:函數類型) 函數類型{}
函數類型基本寫法:() -> Unit
(多個參數) -> 返回類型
//定義兩個函數
val add5 = { i: Int -> i + 5 }
val multiplyBy2 = { i: Int -> i * 2 }
fun main() {
println(multiplyBy2(add5(9)))
}
-----打印出來的Log
28
複製代碼
上面是基本的展現,函數中傳入函數。 下面擴展一下函數:
//定義三個函數
val add5 = { i: Int -> i + 5 }
val multiplyBy2 = { i: Int -> i * 2 }
val sum = { q: Int, w: Int -> q + w }
//關鍵點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))
}
}
// 關鍵點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))
}
}
//關鍵點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() {
// 關鍵點3:
val add5AndMulti2 = add5 andThen multiplyBy2
// 關鍵點4:
val add5ComposeMulti2 = add5 compose multiplyBy2
//關鍵點5:
val sum = sum.toAllSum(add5, multiplyBy2)
println(add5AndMulti2(10))
println(add5ComposeMulti2(10))
println(sum(10,10))
}
-----打印出來的Log
30
25
35
複製代碼
上面實際上就是擴展函數,而後在函數中傳入函數跟返回函數,只有一個參數的則使用了
infix
中綴關鍵字。關鍵點一、二、3都是擴展了函數類型,其中關鍵點1跟2 擴展函數類型爲傳入一個函數參數,關鍵點3擴展函數傳入兩個函數參數
舉例:關鍵點1:函數類型爲
Function<P1,P2>
擴展函數andThen
,傳入函數類型Function1<P2, R>
,返回函數類型Function1<P1, R>
第一個return
:返回函數類型爲fun(p1: P1): R
第二個return
:function.invoke(this.invoke(p1))
,先是傳入this.invoke(p1)
再將這裏返回的值傳入function.invoke()
.
先調用了了andThen
前的函數,再調用andThen
後面的函數。
你們能夠根據這些寫法自定義多種擴展函數~~
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()
}
複製代碼
方法一函數簽名:run(block: () -> R): R
直接傳入代碼塊,並返回R
方法二函數簽名:T.run(block: T.() -> R): R
,傳入的代碼塊block: T.() -> R
,也就是調用者自己的引用,則在block
中則直接可使用T
中的成員變量及函數等
//使用
var sum = run { 5+3 }
println(sum)
var aList = arrayListOf("小明", "小紅", "小黑")
var aListSize = aList.run { size }
println(aListSize)
--------------------打印出來的
8
3
複製代碼
這個
contract {...}
看不懂能夠暫時不用管它,kotlin
中契約的一種寫法,詳情能夠看一下kotlinlang.org/docs/refere…
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
:接受兩個參數,一個是本身自己,一個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
:方法的擴展函數,傳入block: T.() -> Unit
,返回調用者自己,用法與run
一致,可是最後返回的是調用者自己。also
:方法的擴展函數,傳入block: (T) -> Unit
,這裏更前面幾個方法有點不同,block
傳入了T
這個調用者自己,而且函數最後返回調用者自己。 also
用法:var aList = arrayListOf("小明", "小紅", "小黑")
val sizeFinally = aList.also {
println(it.size)
it.add("小黃")
it.add("小綠")
}.size
println(sizeFinally)
---------打印
3
5
複製代碼
let
:方法的擴張函數,傳入block: (T) -> R
,let
方法返回R
。 let
用法:val sizeFinally = aList.let {
println(it.size)
it.add("小黃")
it.add("小綠")
it.size
}
println(sizeFinally)
---------------打印
3
5
複製代碼
tailrec
關鍵字添加到fun
前提示編譯器尾遞歸優化。 尾遞歸:是遞歸的一種形式,遞歸中在調用完本身後沒有其餘操做的稱爲尾遞歸。//符合尾遞歸 能夠加tailrec 關鍵字優化
tailrec fun findListNode(head: ListNode?, value: Int): ListNode? {
head ?: return null
if (head.value == value) return head
return findListNode(head.next, value)
}
// 不符合尾遞歸 由於最後調用完本身還跟n相乘
fun factorial(n:Long):Long{
return n * factorial(n-1)
}
複製代碼
上面的方法中第一種是符合尾遞歸的形式,這種咱們能夠加tailrec
關鍵字,有什麼好處呢?
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)
}
-----------有加了關鍵字tailrec -打印出來的Log
99998
----------沒有加關鍵字打印出來的Log
Exception in thread "main" java.lang.StackOverflowError
複製代碼
由於加了tailrec
關鍵字,其實是優化成了迭代相比遞歸減低了內存空間的開銷。