一. 一等公民函數(shù)
在程序世界里,有且不僅有這么幾種權(quán)力:創(chuàng)建,賦值,傳遞。在JAVA中這些權(quán)力,object 都具備,function 都不具備。object 可以通過參數(shù)傳遞到另一個(gè)對象里,從而兩個(gè)對象可以互相通信。函數(shù)卻不行,兩個(gè)函數(shù)想要通信,必須以對象為介質(zhì)。
以 Java 舉個(gè)例子:函數(shù)a,想要調(diào)用函數(shù)b。雖然a并不關(guān)心函數(shù)b是從哪兒來的,只要函數(shù)b可以完成這個(gè)特定的功能即可。但是在 Java 的世界里函數(shù)必須要依附在一個(gè)對象上,所以函數(shù)a必須依附在對象A上,函數(shù)b必須依附在對象B上,函數(shù)a必須通過一個(gè)對象才能找到函數(shù)b,如下:
public class A {
public void a(Object o) {
System.out.println("a is invoked");
o.getClass().getMethod("b").invoke(o);
}
}
public class B {
public void b() {
System.out.println("b is invoked");
}
}
函數(shù)b可以這樣傳遞給函數(shù)a:
new A().a(new B());
結(jié)果如下:
a is invoked
b is invoked
通過這個(gè)簡單的例子,可以看出,非一等公民的函數(shù)生存條件有多么的惡劣,通訊的阻力有多大。
在函數(shù)是一等公民的世界里,函數(shù)a可以不再依附于對象A而單獨(dú)存在,函數(shù)a可以直接與函數(shù)b交流,不再需要通過對象才能找到函數(shù)b。
函數(shù)是"一等公民"特點(diǎn)指的就是函數(shù)與變量、對象類型一樣,處于平等地位。一等公民函數(shù)有三個(gè)主要的特點(diǎn)。
- 函數(shù)可以賦值給一個(gè)變量。
- 函數(shù)可以作為參數(shù)傳入另一個(gè)函數(shù)。
- 函數(shù)可以作為別的函數(shù)的返回值。
1. 函數(shù)可以賦值給一個(gè)變量
既然函數(shù)可以賦值給一個(gè)變量,那么這個(gè)變量的類型就是函數(shù)類型。Kotlin 中每一個(gè)函數(shù)都有一個(gè)類型,稱為 “函數(shù)類型”,函數(shù)類型是一種數(shù)據(jù)類型,它與 Int、Boolean等數(shù)據(jù)類型 在使用場景上沒有區(qū)別?!?:” 可以取出函數(shù)的地址引用。
例如:
// 計(jì)算一個(gè)矩形面積 (Double, Double) -> Double
fun rectangleArea(width: Double, height: Double): Double {
return width * height
}
fun main(args: Array<String>) {
// 通過::取出rectangleArea函數(shù)的地址 將函數(shù)rectangleArea賦值給一個(gè)變量areaFunction,
// 此時(shí)areaFunction變量的類型為(Double, Double) -> Double
val areaFunction: (Double, Double) -> Double = ::rectangleArea
//靠變量來調(diào)用函數(shù)
val area = areaFunction(50.0, 40.0)
println(area) // 2000.0
}
一些相對簡單的函數(shù)類型:
//無參、無返回值的函數(shù)類型(Unit 返回類型不可省略)
() -> Unit
//接收T類型參數(shù)、無返回值的函數(shù)類型
(T) -> Unit
//接收T類型和A類型參數(shù)、無返回值的函數(shù)類型(多個(gè)參數(shù)同理)
(T,A) -> Unit
//接收T類型參數(shù),并且返回R類型值的函數(shù)類型
(T) -> R
//接收T類型和A類型參數(shù)、并且返回R類型值的函數(shù)類型(多個(gè)參數(shù)同理)
(T,A) -> R
2. 函數(shù)作為參數(shù)
函數(shù)可以作為參數(shù)進(jìn)行傳遞,如果函數(shù)可以作為參數(shù)進(jìn)行傳遞,那么就可以將不同函數(shù)進(jìn)行組合,提高代碼的復(fù)用,代碼會(huì)更簡潔,這部分就可以引出高階函數(shù),類似f(g(x))的形式。
// 計(jì)算一個(gè)矩形面積 (Double, Double) -> Double
fun rectangleArea(width: Double, height: Double): Double {
return width * height
}
//計(jì)算一個(gè)三角形面積
fun triangleArea(bottom: Double, height: Double): Double {
return (bottom * height) / 2
}
//獲取面積
fun getAreaByFun(funName: (Double, Double) -> Double, a: Double, b: Double): Double {
return funName(a, b)
}
fun main(args: Array<String>) {
//參數(shù)為函數(shù),傳入不同的函數(shù)類型
var triangleArea: Double = getAreaByFun(::triangleArea, 10.0, 15.0)
print(triangleArea)
var rectangleArea = getAreaByFun(::rectangleArea, 10.0, 15.0)
print(rectangleArea)
}
3. 函數(shù)可以作為別的函數(shù)的返回值。
函數(shù)可以作為返回值,那么函數(shù)內(nèi)應(yīng)該可以定義函數(shù),并且函數(shù)可以返回函數(shù)內(nèi)定義的函數(shù)。
//獲取面積,返回值是一個(gè)函數(shù)的類型
fun getArea(type: String): (Double, Double) -> Double {
val resultFunction: (Double, Double) -> Double
if (type == "rectangle") {
resultFunction = ::rectangleArea
} else {
resultFunction = ::triangleArea
}
return resultFunction
}
fun main(args: Array<String>) {
//調(diào)用函數(shù)
val rectangleAreaFun: (Double, Double) -> Double = getArea("rectangle")
println("底 10 高 15,計(jì)算三角形面積:${rectangleAreaFun(10.0, 15.0)}")
//調(diào)用函數(shù)
val triangleAreaFun: (Double, Double) -> Double = getArea("triangle")
println("底 10 高 15,計(jì)算長方形面積:${triangleAreaFun(10.0, 15.0)}")
}
二. 函數(shù)式編程
函數(shù)是“一等公民”是函數(shù)式編程的核心概念。
使用表達(dá)式,不用語句:函數(shù)式編程關(guān)心輸入和輸出,即參數(shù)和返回值。在程序中使用表達(dá)式可以有返回值,而語句沒有。例如控制結(jié)構(gòu)中的 if 和 when 結(jié)構(gòu)都屬于表達(dá)式。
高階函數(shù):函數(shù)式編程支持高階函數(shù),所謂的高階函數(shù)就是一個(gè)函數(shù)可以作為另一個(gè)函數(shù)的參數(shù)或返回值。
無副作用:是指函數(shù)執(zhí)行過程會(huì)返回同一個(gè)結(jié)果,不會(huì)修改外部變量,這就是“純函數(shù)”,同樣的輸入?yún)?shù)一定會(huì)有同樣的輸出結(jié)果。
Kotlin 語言支持函數(shù)式編程,提供了函數(shù)類型、高階函數(shù) 和 Lambda 表達(dá)式。
四. 匿名函數(shù)
匿名函數(shù)就是沒有名字的函數(shù)對象(注意匿名函數(shù)不是函數(shù),而是函數(shù)類型的對象),大多數(shù)情況下我們定義的函數(shù)都是具名函數(shù)(有名字的函數(shù))。匿名函數(shù)就是只定義參數(shù)列表、返回值類型和函數(shù)體,把一個(gè)匿名函數(shù)賦給一個(gè)沒有定義函數(shù)體的函數(shù)對象。那這種沒有匿名函數(shù)我們怎么調(diào)用呢?答案是無法直接調(diào)用。匿名函數(shù)可以賦值給一個(gè)變量,或者當(dāng)作實(shí)參直接傳遞給一個(gè)函數(shù)類型的形參。
具名函數(shù)如下:
fun sum(arg1 : Int,arg2 : Int): Int{
return arg1 + arg2
}
這個(gè)函數(shù)的名字就叫sum。
那匿名函數(shù)定義:
fun(arg1 : Int, arg2 : Int) : Int{
return arg1 + arg2
}
這樣寫還不行,因?yàn)閴焊恢朗裁磿r(shí)候用,所以我們需要付給一個(gè)引用,用來保存它,然后在需要使用的時(shí)候調(diào)用:
val sum = fun(arg1 : Int, arg2 : Int) : Int{
return arg1 + arg2
}
三. lambda表達(dá)式
1. 定義
Lambda 表達(dá)式的本質(zhì)其實(shí)就是匿名函數(shù)。而函數(shù)其實(shí)就是功能(function),匿名函數(shù),就是匿名的功能代碼了。Lambda表達(dá)式才是與高階函數(shù)的絕配,平時(shí)我們給高階函數(shù)中的函數(shù)類型參數(shù)傳遞值時(shí),一般都會(huì)選擇傳入Lambda表達(dá)式,因?yàn)樗銐蚝啙嵟c強(qiáng)大。
Lambda表達(dá)式的本質(zhì)是匿名函數(shù),而匿名函數(shù)的本質(zhì)是函數(shù)類型的對象。因此,Lambda表達(dá)式、匿名函數(shù)、雙冒號+函數(shù)名這三個(gè)東西,都是函數(shù)類型的對象,他們都能夠賦值給變量以及當(dāng)作函數(shù)的參數(shù)傳遞!
創(chuàng)建一個(gè)函數(shù)類型的對象(函數(shù)字面量)有三種方式:
- 函數(shù)引用,::函數(shù)名,表示函數(shù)引用,會(huì)拿到一個(gè) 函數(shù)的對象 ;注意不是函數(shù)本身。
- 匿名函數(shù),沒有名字的函數(shù)類型的對象。
- lambda是匿名函數(shù)的表現(xiàn)形式也就同上。
通常這樣寫匿名函數(shù):
val addFun = fun(x: Int, y: Int): Int {
return x + y
}
使用lambda表達(dá)式可以簡化:
//lambda表達(dá)式
val addLambda = { x: Int, y: Int -> x * y }
2. 語法
- 總是被大括號擴(kuò)著
- 其參數(shù)(如果存在)在->之前聲明(參數(shù)類型可以省略)
- 函數(shù)體(如果存在)在->后面

- 無參數(shù)的情況
val/var 變量名 = { 操作代碼 }
val sum = { }
- 有參數(shù)的情況
val/var 變量名:(參數(shù)類型,參數(shù)類型,...)->返回值類型 = (參數(shù)1,參數(shù)2,...->操作參數(shù)的代碼)
val sum:(Int,Int)->Int = {a,b->a+b}
可等價(jià)于
//此種寫法:即表達(dá)式的返回值類型會(huì)根據(jù)操作代碼自推導(dǎo)出來
val/var 變量名 = {參數(shù)1:類型,參數(shù)2:類型...->操作代碼}
val sum={a:Int,b:Int ->a+b}
3.lambda表達(dá)式作為函數(shù)中的參數(shù)的時(shí)候
fun sum(a:Int,參數(shù)名:(參數(shù)1:類型,參數(shù)2:類型...)->表達(dá)式返回類型){ ... }
簡化寫法
當(dāng) lambda 表達(dá)式只接受一個(gè)參數(shù)時(shí),該參數(shù)可以省略,使用時(shí)用 it 來表示該參數(shù):
add("xxx", { s -> s + "xxx" })
//等同于
add("xxx", { it + "xxx" })復(fù)制代碼
當(dāng)函數(shù)最后一個(gè)參數(shù)為函數(shù)時(shí),該函數(shù)可以寫在 () 外,并用 {} 包裹
add("xxx", { s -> s + "xxx" })
//等同于
add("xxx") { s -> s + "xxx" }
//等同于
foo("xxx") { it + "xxx" }復(fù)制代碼
當(dāng)函數(shù)只有一個(gè)參數(shù),且該參數(shù)為函數(shù)時(shí),可以直接省去 ()
foo({ s -> s + "xxx" })
//等同于
foo { s -> s + "xxx" }復(fù)制代碼
當(dāng)參數(shù)在函數(shù)體中沒有引用時(shí),可以將其設(shè)為 _,若此時(shí)只有一個(gè)參數(shù)(且該參數(shù)沒有被引用),則可以直接省略該參數(shù);若有兩個(gè)或以上的參數(shù),就算全部都沒有被引用,也不可以省略
foo({ s -> print("xxx") })
//等同于
foo({ _ -> print("xxx") })
//等同于
foo({ print("xxx") })
//等同于
foo { print("xxx") }
3. 返回值
lambda表達(dá)式返回值總是返回函數(shù)體內(nèi)部最后一行表達(dá)式的值。
lambda表達(dá)式語法缺少指定函數(shù)的返回類型的能力,因此Lambda表達(dá)式不能指定返回值類型,當(dāng)需要顯式指定返回類型時(shí),可以使用匿名函數(shù)。
fun(x: Int, y: Int): Int {
return x + y
}
4. 帶接收者的Lambda
目前講到的lambda都是普通lambda,lambda中還有一種類型:帶接收者的lambda。
帶接受者的lambda的類型定義:
A.() -> C
表示可以在A類型的接收者對象上調(diào)用并返回一個(gè)C類型值的函數(shù)。
帶接收者的lambda好處是,在lambda函數(shù)體可以無需任何額外的限定符的情況下,直接使用接收者對象的成員(屬性或方法),亦可使用this訪問接收者對象。
Kotlin的標(biāo)準(zhǔn)庫中就有提供帶接收者的lambda表達(dá)式:with和apply。
在kotlin中,提供了指定的接受者對象調(diào)用Lambda表達(dá)式的功能。在函數(shù)字面值的函數(shù)體中,可以調(diào)用該接收者對象上的方法而無需任何額外的限定符。它類似于擴(kuò)展函數(shù),它允許在函數(shù)體內(nèi)訪問接收者對象的成員。
val iop = fun Int.( other : Int) : Int = this + other
println(2.iop(3))
結(jié)果為5
5. 在android使用例子
Java8 開始支持 Lambda 表達(dá)式
Java 在使用 單 抽象方法的接口時(shí),允許使用 lambda 表達(dá)式
在 Kotlin 中就不支持這么寫了,因?yàn)闆]有必要(可以直接傳函數(shù)對象)
但在 Kotlin 和 Java 做交互的時(shí)候可以這么寫。
首先來通過一個(gè)例子直觀感受一下lambda表達(dá)式。Android開發(fā)中經(jīng)常會(huì)給一個(gè)Button設(shè)置OnClickListener。比如我們需要讓按鈕點(diǎn)擊后消失,平時(shí)我們可能是這樣寫的:
//傳統(tǒng)Java式寫法
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
view.setVisibility(View.GONE);
}
});
而在Kotlin中,使用函數(shù)式語法,我們可以這樣寫:
//Kotlin函數(shù)式寫法
mButton.setOnClickListener {
it.visibility = View.GONE
}
直觀來講,似乎跟我們平時(shí)的寫法差別有點(diǎn)大,比如,函數(shù)調(diào)用的小括號不見了,匿名內(nèi)部類直接被一個(gè)函數(shù)體取代了,View參數(shù)不見了,分號也消失了,還有那個(gè)it是什么……其實(shí),就像數(shù)學(xué)公式推導(dǎo)一樣,精簡的寫法也是通過一步一步簡化來的。下面就讓我們來看一下代碼段2的“推導(dǎo)過程”:
- 首先,代碼段1轉(zhuǎn)換為Kotlin代碼,并替換為函數(shù)式寫法:
mButton.setOnClickListener({ view: View ->
view.visibility = View.GONE
})
這段代碼非常清晰,花括號包裹的是一段lambda表達(dá)式,可以把它作為實(shí)參傳遞給函數(shù),這一步把匿名內(nèi)部類省略掉了;另外也干掉了分號,因?yàn)樵贙otlin中行末尾的分號可以省略;最后還省略了set方法,在Kotlin中,會(huì)默認(rèn)把對屬性的直接訪問轉(zhuǎn)換成get/set方法調(diào)用。
- 然后,根據(jù)Kotlin的語法約定,如果lambda表達(dá)式是函數(shù)調(diào)用的最后一個(gè)實(shí)參,就可以把它挪到小括號外面:
mButton.setOnClickListener() { view: View ->
view.visibility = View.GONE
}
- 當(dāng)lambda是函數(shù)的唯一實(shí)參,就可以去掉空的小括號對:
mButton.setOnClickListener { view: View ->
view.visibility = View.GONE
}
- 如果lambda的參數(shù)的類型可以被編譯器推導(dǎo)出來,就可以省略它:
mButton.setOnClickListener { view ->
view.visibility = View.GONE
}
- 最后,如果這個(gè)lambda只有一個(gè)參數(shù),并且這個(gè)參數(shù)的類型可以被推斷出來(也就是同時(shí)滿足3和4),那么這個(gè)參數(shù)也可以省略掉。代碼中引用這個(gè)參數(shù)的地方可以通過編譯器自動(dòng)生成的名稱it來替代:
mButton.setOnClickListener {
it.visibility = View.GONE
}
經(jīng)過上述5個(gè)步驟,就得到了最簡潔、最清晰的代碼段。
四. 閉包
1. 定義
我們都知道,程序的變量分為全局變量和局部變量,全局變量,顧名思義,其作用域是當(dāng)前文件甚至文件外的所有地方;而局部變量,我們只能再其有限的作用域里獲取。
那么,如何在外部調(diào)用局部變量呢?答案就是——閉包,與此給閉包下個(gè)定義:閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)
閉包,即是函數(shù)中包含函數(shù),這里的函數(shù)我們可以包含(Lambda表達(dá)式,匿名函數(shù),局部函數(shù),對象表達(dá)式)。
fun test1(){
fun test2(){
}
}
2. 閉包使用
我們來看一個(gè)閉包的例子:
fun returnFun(): () -> Int {
var count = 0
return { count++ }
}
fun main() {
val function = returnFun()
val function2 = returnFun()
println(function()) // 0
println(function()) // 1
println(function()) // 2
println(function2()) // 0
println(function2()) // 1
println(function2()) // 2
}
returnFun返回了一個(gè)函數(shù),這個(gè)函數(shù)沒有入?yún)?,返回值是Int。我們可以用變量接收它,還可以調(diào)用它。function和function2分別是創(chuàng)建的兩個(gè)函數(shù)實(shí)例。
每調(diào)用一次function(),count都會(huì)加一,說明count 被function持有了而且可以被修改。而function2和function的count是獨(dú)立的,不是共享的。
通過 jadx 反編譯可以看到:
public final class ClosureKt {
@NotNull
public static final Function0<Integer> returnFun() {
IntRef intRef = new IntRef();
intRef.element = 0;
return (Function0) new 1<>(intRef);
}
public static final void main() {
Function0 function = returnFun();
Function0 function2 = returnFun();
System.out.println(((Number) function.invoke()).intValue());
System.out.println(((Number) function.invoke()).intValue());
System.out.println(((Number) function2.invoke()).intValue());
System.out.println(((Number) function2.invoke()).intValue());
}
}
被閉包引用的 int 局部變量,會(huì)被封裝成 IntRef 這個(gè)類。這個(gè) IntRef 里面保存著 int 變量,原函數(shù)和閉包都可以通過 intRef 來讀寫 int 變量。Kotlin 正是通過這種辦法使得局部變量可修改。除了 IntRef,還有 LongRef,F(xiàn)loatRef 等,如果是非基礎(chǔ)類型,就統(tǒng)一用 ObjectRef 即可。
3. 捕獲變量
閉包可以訪問函數(shù)體之外的變量,這個(gè)過程稱為捕獲變量。
// 全局變量
var value = 0
fun main(args: Array<String>?) {
// 局部變量
var localValue = 20
val result = { a: Int ->
value++
localValue++
val c = a + value + localValue
println(c)
}
result(30)
println("value = $value")
println("localValue = $localValue")
}
System.out: 52
System.out: value = 1
System.out: localValue = 21
閉包是捕獲 value 和 localValue 變量的 Lambda 表達(dá)式。
Java 與 Koltin 中 Lambda 捕獲局部變量區(qū)別
在函數(shù)不是“一等公民”的 Java 這里,匿名類其實(shí)就是代替閉包而存在的。只不過 Java 嚴(yán)格要求所有函數(shù)都需要在類里面,所以巧妙的把“聲明一個(gè)函數(shù)”這樣的行為變成了“聲明一個(gè)接口”或“重寫一個(gè)方法”。匿名類也可以捕獲當(dāng)前環(huán)境的 final 局部變量。但和閉包不一樣的是,匿名類無法修改捕獲的局部變量(final 不可修改)。而匿名類能引用 final 的局部變量,是因?yàn)樵诰幾g階段,會(huì)把該局部變量作為匿名類的構(gòu)造參數(shù)傳入。因?yàn)槟涿愋薷牡淖兞坎皇钦嬲木植孔兞浚亲约旱臉?gòu)造參數(shù),外部局部變量并沒有被修改。所以 Java 編譯器不允許匿名類引用非 final 變量。jdk7在 Lambda 體中只能讀取局部變量,不能修改局部變量。而 kotlin 中沒有這個(gè)限制,可以讀取和修改局部變量。如下面代碼:
// 聲明了一個(gè)Java代碼接口
public interface Clickable {
void onClick();
}
// Java中的Lambda表達(dá)式局部變量捕獲
public class Closure {
private void closure(Clickable clickable) {
clickable.onClick();
}
public void main(ArrayList<String> args) {
int count = 0;
closure(() -> {
count += 1; // 編譯錯(cuò)誤,count需要使用final修飾
});
System.out.println(count);
}
}
這樣的Java代碼是編譯不過的,必須設(shè)置為 count 為 final 才能通過編譯,但又不能對 count 進(jìn)行修改,如果非要修改 count 只能把 count 聲明為 Closure 的成員變量。
對比 Kotlin 代碼實(shí)現(xiàn):
class Closure {
private fun closure(clickable: Clickable) {
clickable.onClick()
}
fun main(args: Array<String>) {
var count: Int = 0
closure(Clickable { count += 1 }) // 編譯正常
println(count) // 2
}
}
再來看一個(gè)閉包的例子:
fun returnFun(): () -> Int {
var count = 0
return { count++ }
}
fun main() {
val function = returnFun()
val function2 = returnFun()
println(function()) // 0
println(function()) // 1
println(function()) // 2
println(function2()) // 0
println(function2()) // 1
println(function2()) // 2
}
每調(diào)用一次function(),count都會(huì)加一,說明count 被function持有了而且可以被修改。而function2和function的count是獨(dú)立的,不是共享的。
五. 擴(kuò)展函數(shù)
擴(kuò)展函數(shù)數(shù)是指在一個(gè)類上增加一種新的行為,甚至我們沒有這個(gè)類代碼的訪問權(quán)限。在Java中,通常會(huì)實(shí)現(xiàn)很多帶有static方法的工具類,而Kotlin中擴(kuò)展函數(shù)的一個(gè)優(yōu)勢是我們不需要在調(diào)用方法的時(shí)候把整個(gè)對象當(dāng)作參數(shù)傳入,它表現(xiàn)得就像是屬于這個(gè)類的一樣,而且我們可以使用this關(guān)鍵字和調(diào)用所有public方法。
fun 被擴(kuò)展類名.擴(kuò)展函數(shù)名( 參數(shù) ){
//實(shí)現(xiàn)代碼
}
Java調(diào)用擴(kuò)展函數(shù):
擴(kuò)展類名Kt.擴(kuò)展函數(shù)名(參數(shù));
六. 內(nèi)聯(lián)函數(shù)
1. inline
如果沒有內(nèi)聯(lián)修飾符標(biāo)記函數(shù),在使用lambda帶來的性能開銷。舉個(gè)接收函數(shù)類型的例子:
//callAction 接受一個(gè)函數(shù)類型(lambda)
private fun callAction(action: () -> Unit) {
println("call Action before")
action()
println("call Action after")
}
fun main(args: Array<String>) {
callAction {
println("call action")
}
}
反編譯為:
public final void main(@NotNull String[] args) {
callAction((Function0)null.INSTANCE);
}
private final void callAction(Function0 action) {
String var2 = "call Action before";
boolean var3 = false;
System.out.println(var2);
action.invoke();
var2 = "call Action after";
var3 = false;
System.out.println(var2);
}
由此可見當(dāng)調(diào)用callAction(action: () -> Unit) 時(shí),傳遞的lambda會(huì)被Function0所代替,而Function0是一個(gè)被定義為如下的接口:
public interface Function0<out R> : Function<R> {
public operator fun invoke(): R
}
在調(diào)用callAction時(shí),編譯器會(huì)額外生成一個(gè)Function0的實(shí)例傳遞給callAction,內(nèi)部會(huì)調(diào)用 Function0 的 invoke() 方法。到目前為止,我們知道使用lambda會(huì)帶來額外的性能開銷。通過內(nèi)聯(lián)函數(shù)消除lambda帶來的運(yùn)行時(shí)開銷。
被inline標(biāo)記的函數(shù)就是內(nèi)聯(lián)函數(shù),其原理就是:在編譯時(shí)期,把調(diào)用這個(gè)函數(shù)的地方用這個(gè)函數(shù)的方法體進(jìn)行替換。
在函數(shù)被使用的時(shí)候編譯器并不會(huì)生成函數(shù)調(diào)用的代碼,而是使用函數(shù)實(shí)現(xiàn)的真實(shí)代碼替換每一次的函數(shù)調(diào)用。還是拿 callAction(action: () -> Unit) 方法舉例,當(dāng)給該函數(shù)添加inline修飾符后,編譯后的調(diào)用代碼如下
public final void main(@NotNull String[] {
...省略無關(guān)緊要的代碼
System.out.println("call Action before");
System.out.println("call action");
System.out.println("call Action after");
}
總結(jié)下:
- 被inline修飾的函數(shù)叫內(nèi)聯(lián)函數(shù)。
- 內(nèi)聯(lián)函數(shù)會(huì)在被調(diào)用的位置內(nèi)聯(lián)。內(nèi)聯(lián)函數(shù)的代碼會(huì)被拷貝到使用它的位置,并把lambda替換到其中。
在kotlin中l(wèi)ambda 表達(dá)式會(huì)被正常地編譯成匿名類。這表示每調(diào)用一次lambda 表達(dá)式,一個(gè)額外的類就會(huì)被創(chuàng)建。并且如果lambda 捕捉了某個(gè)變量,那么每次調(diào)用的時(shí)候都會(huì)創(chuàng)建一個(gè)新的對象。這會(huì)帶來運(yùn)行時(shí)的額外開銷,導(dǎo)致使用lambda 比使用一個(gè)直接執(zhí)行相同代碼的函數(shù)效率更低。
如果使用 inline 修飾符標(biāo)記一個(gè)函數(shù),在函數(shù)被使用的時(shí)候編譯器并不會(huì)生成函數(shù)調(diào)用的代碼,而是使用函數(shù)實(shí)現(xiàn)的真實(shí)代碼替換每一次的函數(shù)調(diào)用。
2. noinline
雖然內(nèi)聯(lián)非常好用,但是會(huì)出現(xiàn)這么一個(gè)問題,就是內(nèi)聯(lián)函數(shù)的參數(shù)(ps:參數(shù)是函數(shù),比如上面的body函數(shù))如果在內(nèi)聯(lián)函數(shù)的方法體內(nèi)被其他非內(nèi)聯(lián)函數(shù)調(diào)用,就會(huì)報(bào)錯(cuò)。
舉個(gè)栗子:
inline fun <T> mehtod(lock: Lock, body: () -> T): T {
lock.lock()
try {
otherMehtod(body)//會(huì)報(bào)錯(cuò)
return body()
} finally {
lock.unlock()
}
}
fun <T> otherMehtod(body: ()-> T){
}
原因:因?yàn)閙ethod是內(nèi)聯(lián)函數(shù),所以它的形參也是inline的,所以body就是inline的,但是在編譯時(shí)期,body已經(jīng)不是一個(gè)函數(shù)對象,而是一個(gè)具體的值,然而otherMehtod卻要接收一個(gè)body的函數(shù)對象,所以就編譯不通過了
解決方法:當(dāng)然就是加noinline了,它的作用就已經(jīng)非常明顯了.就是讓內(nèi)聯(lián)函數(shù)的形參函數(shù)不是內(nèi)聯(lián)的,保留原有的函數(shù)特征.
具體操作:
fun main(args: Array<String>) {
val lock = ReentrantLock()
mehtod(lock,{"body方法體"})
}
inline fun <T> mehtod(lock: Lock, noinline body: () -> T): T {
lock.lock()
try {
otherMehtod(body)
return body()
} finally {
lock.unlock()
}
}
fun <T> otherMehtod(body: ()-> T){
}
這樣編譯時(shí)期這個(gè)body函數(shù)就不會(huì)被內(nèi)聯(lián)了
反編譯看下
public static final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, "args");
ReentrantLock lock = new ReentrantLock();
//這里是生成了一個(gè)函數(shù)對象
Function0 body$iv = (Function0)null.INSTANCE;
((Lock)lock).lock();
try {
otherMehtod(body$iv);
Object var3 = body$iv.invoke();
} finally {
((Lock)lock).unlock();
}
}
public static final Object mehtod(@NotNull Lock lock, @NotNull Function0 body) {
Intrinsics.checkParameterIsNotNull(lock, "lock");
Intrinsics.checkParameterIsNotNull(body, "body");
lock.lock();
Object var3;
try {
otherMehtod(body);
var3 = body.invoke();
} finally {
InlineMarker.finallyStart(1);
lock.unlock();
InlineMarker.finallyEnd(1);
}
return var3;
}
public static final void otherMehtod(@NotNull Function0 body) {
Intrinsics.checkParameterIsNotNull(body, "body");
}
3. crossinline
很少用到crossinline修飾符,什么是crossinline呢,crossinline 的作用是內(nèi)聯(lián)函數(shù)中讓被標(biāo)記為crossinline 的lambda表達(dá)式不允許非局部返回。
在kotlin中,return 只可以用在有名字的函數(shù),或者匿名函數(shù)中,使得該函數(shù)執(zhí)行完畢。
而針對lambda表達(dá)式,你不能直接使用return
你可以使用return+label的形式,將這個(gè)lambda結(jié)束。
但是
若你的lambda應(yīng)用在一個(gè)內(nèi)聯(lián)函數(shù)的時(shí)候,這時(shí)候你可以在lambda中使用return
可以這么理解,內(nèi)聯(lián)函數(shù)在編譯的時(shí)候,將相關(guān)的代碼貼入你調(diào)用的地方。
lambda表達(dá)式就是一段代碼而已,這時(shí)候你在lambda中的return,相當(dāng)于在你調(diào)用的方法內(nèi)return
crossinline就是為了讓其不能直接return。
函數(shù)式編程——閉包
理解Kotlin函數(shù)式編程
函數(shù)式編程-Kotlin
函數(shù)式編程——閉包
kotlin 閉包簡單例子
理解Kotlin函數(shù)式編程
kotlin的內(nèi)聯(lián)函數(shù)的使用
inline,包治百病的性能良藥
理解Kotlin函數(shù)式編程