從Java到Kotlin(五)

函數(shù)與Lambda表達(dá)式

目錄

一、函數(shù)聲明與調(diào)用
二、參數(shù)和返回值
三、單表達(dá)式函數(shù)
四、函數(shù)作用域
五、泛型函數(shù)
六、尾遞歸函數(shù)
七、中綴表示法
八、Lambda表達(dá)式的語(yǔ)法
九、高階函數(shù)與Lambda表達(dá)式
十、匿名函數(shù)
十一、內(nèi)聯(lián)函數(shù)


一、函數(shù)聲明與調(diào)用

Java 中的方法使用 void 關(guān)鍵字聲明:

void foo(){}

Kotlin 中的函數(shù)使用 fun 關(guān)鍵字聲明:

fun foo(){}

用法相似,加入有一個(gè) User 類(lèi),里面有一個(gè) foo() 函數(shù),調(diào)用函數(shù)的代碼如下:
Java代碼

new User().foo();

Kotlin代碼

User().foo()

二、參數(shù)和返回值

聲明有參數(shù)的函數(shù),代碼如下:
Java代碼

void foo(String str, int i) {}

Kotlin代碼

fun foo(str: String, i: Int) {}

Java先定義類(lèi)型,后命名;Kotlin先命名,后定義類(lèi)型,中間用冒號(hào):分隔。兩者都是多個(gè)參數(shù)中間用逗號(hào),分隔。
如函數(shù)有返回值,代碼如下:
Java代碼

String foo(String str, int i) {
    return "";
}

Kotlin代碼

fun foo(str: String, i: Int): String {
   return ""
}

Java是把void替換成返回值的類(lèi)型,而Kotlin是把返回值聲明在函數(shù)的末尾,并用冒號(hào):分隔。
兩種語(yǔ)言聲明參數(shù)和返回值的方式有點(diǎn)相似,而Kotlin還有更強(qiáng)大的功能,例如默認(rèn)參數(shù)命名參數(shù),如下所示:
函數(shù)參數(shù)可以有默認(rèn)值,當(dāng)沒(méi)有給參數(shù)指定值的時(shí)候,使用默認(rèn)值

//給i指定默認(rèn)值為1
fun foo(str: String, i: Int = 1) {
    println("$str  $i")
}
//調(diào)用該函數(shù),這個(gè)時(shí)候可以只傳一個(gè)參數(shù)
foo("abc")
//運(yùn)行代碼,得到結(jié)果為: abc  1

如果有默認(rèn)值的參數(shù)在無(wú)默認(rèn)值的參數(shù)之前,要略過(guò)有默認(rèn)值的參數(shù)去給無(wú)默認(rèn)值的參數(shù)指定值,要使用命名參數(shù)來(lái)指定值,有點(diǎn)繞我們看代碼:

//有默認(rèn)值的參數(shù)在無(wú)默認(rèn)值的參數(shù)之前
fun foo(i: Int = 1, str: String) {
    println("$str  $i")
}
//foo("hello")  //編譯錯(cuò)誤
foo(str = "hello")  //編譯通過(guò),要使用參數(shù)的命名來(lái)指定值
//運(yùn)行代碼,得到結(jié)果為: hello  1
  • 可變數(shù)量的參數(shù)
    函數(shù)的參數(shù)可以用 vararg 修飾符標(biāo)記,表示允許將可變數(shù)量的參數(shù)傳遞給函數(shù),如下所示:
//用 vararg 修飾符標(biāo)記參數(shù)
fun <T> asList(vararg ts: T): List<T> {
    val result = ArrayList<T>()
    for (t in ts) // ts is an Array
        result.add(t)
    return result
}

val a = arrayOf(1, 2, 3)
//*a代表把a(bǔ)里所有元素
val list = asList(-1, 0, *a, 4)
//運(yùn)行代碼,得到結(jié)果為: [-1, 0, 1, 2, 3, 4]

三、單表達(dá)式函數(shù)

在Kotlin中,如果函數(shù)的函數(shù)體只有一條語(yǔ)句,并且有返回值,那么可以省略函數(shù)體的大括號(hào),變成單表達(dá)式函數(shù)。如下所示:

//函數(shù)體內(nèi)只有一條語(yǔ)句,且有返回值
fun foo(): String{
    return "abc"
}
//這時(shí)可以省略大括號(hào),變成單表達(dá)式函數(shù)
fun foo() = "abc"

四、函數(shù)作用域

在 Kotlin 中函數(shù)可以在文件頂層聲明,這意味著你不需要像一些語(yǔ)言如 Java 那樣創(chuàng)建一個(gè)類(lèi)來(lái)保存一個(gè)函數(shù)。此外除了頂層函數(shù),Kotlin 中函數(shù)也可以聲明在局部作用域、作為成員函數(shù)以及擴(kuò)展函數(shù)。

1. 成員函數(shù)

成員函數(shù)是指在類(lèi)或?qū)ο罄锒x的函數(shù)。

Java代碼:

class User {
    //在類(lèi)里定義函數(shù)。
    void foo() {}
}
//調(diào)用
new User().foo();

Kotlin代碼:

class User() {
    //在類(lèi)里定義函數(shù)。
    fun foo() {}
}
//調(diào)用
User().foo()

2. 局部函數(shù)

Kotlin支持在函數(shù)內(nèi)嵌套另一個(gè)函數(shù),嵌套在里面的函數(shù)成為局部函數(shù),如下所示:

fun foo() {
    println("outside")
    fun inside() {
        println("inside")
   }
   inside()
}

//調(diào)用foo()函數(shù)
foo()

運(yùn)行代碼,得到結(jié)果

而Java中沒(méi)有局部函數(shù)這一概念。

五、泛型函數(shù)

泛型參數(shù)使用尖括號(hào)指定,如下所示:
Java代碼

<T> void print(T t) {
}

<T> List<T> printList(T t) {
}

Kotlin代碼

fun <T> printList(item: T) {
}

fun <T> printList(item: T): List<T> {
}

六、尾遞歸函數(shù)

尾遞歸函數(shù)是一個(gè)遞歸函數(shù),用關(guān)鍵字tailrec來(lái)修飾,函數(shù)必須將其自身調(diào)用作為它執(zhí)行的最后一個(gè)操作。當(dāng)一個(gè)函數(shù)用tailrec修飾符標(biāo)記并滿足所需的形式時(shí),編譯器會(huì)優(yōu)化該遞歸,留下一個(gè)快速而高效的基于循環(huán)的版本,無(wú)堆棧溢出的風(fēng)險(xiǎn),舉個(gè)例子:
先看一段代碼

fun count(x: Int = 1): Int = if (x == 10) x else count(x - 1)

上面的count()函數(shù)是一個(gè)死循環(huán),當(dāng)我們調(diào)用count()函數(shù)后,會(huì)報(bào)StackOverflowError。這時(shí)可以用tailrec修飾符標(biāo)記該遞歸函數(shù),并將其自身調(diào)用作為它執(zhí)行的最后一個(gè)操作,如下所示:

tailrec fun count(x: Int = 1): Int = if (x == 10) x else count(x - 1)

再次運(yùn)行代碼,無(wú)堆棧溢出。

七、中綴表示法

中綴表示法是調(diào)用函數(shù)的另一種方法。如果要使用中綴表示法,需要用infix 關(guān)鍵字來(lái)修飾函數(shù),且要滿足下列條件:

  • 它們必須是成員函數(shù)或擴(kuò)展函數(shù);
  • 它們必須只有一個(gè)參數(shù);
  • 其參數(shù)不得接受可變數(shù)量的參數(shù)。

下面來(lái)舉個(gè)例子:

//擴(kuò)展函數(shù)
infix fun String.removeLetter(str: String): String {
    //this指調(diào)用者
    return this.replace(str, "")
}

//調(diào)用
var str = "hello world"
//不使用中綴表示法
println(str.removeLetter("h")) //輸出ello world
//使用中綴表示法
println(str removeLetter "d")  //輸出hello worl
//使用中綴表示法調(diào)用str removeLetter "d"等同于調(diào)用str.removeLetter("d")

//還可以連續(xù)調(diào)用
println(str.removeLetter("h").removeLetter("d").removeLetter("l")) // 輸出 eo wor
println(str removeLetter "h" removeLetter "d" removeLetter "l") // 輸出 eo wor

八、Lambda表達(dá)式的語(yǔ)法

Lambda表達(dá)式的語(yǔ)法如下:

  • Lambda 表達(dá)式總是括在大括號(hào)中;
  • 其參數(shù)(如果有的話)在 -> 之前聲明(參數(shù)類(lèi)型可以省略);
  • 函數(shù)體(如果存在的話)在 -> 后面。

舉個(gè)例子:

//這是一個(gè)Lambda表達(dá)式的完整語(yǔ)法形式
val sum = { x: Int, y: Int -> x + y }
//Lambda表達(dá)式在大括號(hào)中
//參數(shù) x 和 y 在 -> 之前聲明
//參數(shù)聲明放在大括號(hào)內(nèi),并有參數(shù)類(lèi)型標(biāo)注
//函數(shù)體 x + y 在 -> 后面

val i: Int = sum(1, 2)
println(i) //輸出結(jié)果為 3

如果Lambda表達(dá)式自動(dòng)推斷的返回類(lèi)型不是Unit,那么在Lambda表達(dá)式函數(shù)體中,會(huì)把最后一條表達(dá)式的值當(dāng)做是返回值。所以上面的常量sum 的返回值是Int類(lèi)型。如果要指定常量sum的返回值為Int類(lèi)型,可以這樣寫(xiě):

val sum: (Int, Int) -> Int = { x, y -> x + y }

val i: Int = sum(1, 2)
println(i) //輸出結(jié)果為 3

當(dāng)Lambda表達(dá)式只有一個(gè)參數(shù)的時(shí)候,那么它將可以省略這個(gè)唯一的參數(shù)的定義,連同->也可以省略。如下所示:

//當(dāng)Lambda表達(dá)式只有一個(gè)參數(shù)的時(shí)候
val getInt: (Int) -> Int = { x -> x + 1 }
val int = getInt(2)
println(int)  //輸出結(jié)果為:3

//可以省略這個(gè)參數(shù)的定義
//并且將隱含地獎(jiǎng)這個(gè)參數(shù)命名為 it
val sum: (Int) -> Int = { it + 1 }
val int = sum(2)
println(int)  //輸出結(jié)果為:3

上面說(shuō)到如果Lambda表達(dá)式自動(dòng)推斷的返回類(lèi)型不是Unit,那么在Lambda表達(dá)式函數(shù)體中,會(huì)把最后一條表達(dá)式的值當(dāng)做是返回值。舉個(gè)例子:

var sum: (Int) -> Int = {
      val i: Int = it + 1
      val j: Int = i + 3
      val k: Int = it + j - i
      i
      k
      j
  }
println(sum(1)) 
//輸出結(jié)果為 5,也就是 j 的值

九、高階函數(shù)與Lambda表達(dá)式

高階函數(shù)是將函數(shù)用作參數(shù)或返回值的函數(shù),如下所示:

fun getName(name: String): String {
    return name
}

fun printName(a: String, name: (str: String) -> String): String {
    var str = "$a${name("Czh")}"
    return str
}

//調(diào)用
println(printName("Name:", ::getName))
//運(yùn)行代碼,輸出 Name:Czh

上面代碼中name: (str: String) -> String是一個(gè)函數(shù),擁有函數(shù)類(lèi)型() -> String,接收一個(gè)String參數(shù),當(dāng)我們執(zhí)行var str = "$a${name("Czh")}"這行代碼的時(shí)候,相當(dāng)于執(zhí)行了var str = "$a${getName("Czh")}",并返回了字符串"Czh"。當(dāng)我們調(diào)用printName("Name:", ::getName)時(shí),將函數(shù)作為參數(shù)傳入高階函數(shù),需要在該函數(shù)前加兩個(gè)冒號(hào)::作為標(biāo)記。

Kotlin提供了Lambda表達(dá)式來(lái)讓我們更方便地傳遞函數(shù)參數(shù)值。Lambda表達(dá)式總是被大括號(hào)括著;如果有參數(shù)的話,其參數(shù)在 -> 之前聲明,參數(shù)類(lèi)型可以省略;如果存在函數(shù)體的話,函數(shù)體在-> 后面,如下所示:

println(printName("Name:", { name -> getName("Czh") }))
//運(yùn)行代碼,輸出 Name:Czh

如果函數(shù)的最后一個(gè)參數(shù)是一個(gè)函數(shù),并且你傳遞一個(gè)Lambda表達(dá)
式作為相應(yīng)的參數(shù),你可以在圓括號(hào)()之外指定它,如下所示:

println(printName("Name:") { name -> getName("Czh") })
//運(yùn)行代碼,輸出 Name:Czh

十、匿名函數(shù)

匿名函數(shù)與常規(guī)函數(shù)一樣,只是省略了函數(shù)名稱(chēng)而已。舉個(gè)例子

fun(x: Int, y: Int): Int = x + y

匿名函數(shù)函數(shù)體是表達(dá)式,也可以是代碼段,如下所示:

fun(x: Int, y: Int): Int {
    return x + y
}

上面高階函數(shù)的例子中的printName函數(shù)的第二個(gè)參數(shù)也可以傳入一個(gè)匿名函數(shù),如下所示:

println(printName("Name:", fun(str: String): String { return "Czh" }))
//運(yùn)行代碼,輸出 Name:Czh

十一、內(nèi)聯(lián)函數(shù)

1.內(nèi)聯(lián)函數(shù)

使用高階函數(shù)會(huì)帶來(lái)一些運(yùn)行時(shí)的效率損失。每一個(gè)函數(shù)都是一個(gè)對(duì)象,并且會(huì)捕獲一個(gè)閉包。 即那些在函數(shù)體內(nèi)會(huì)訪問(wèn)到的變量。 內(nèi)存分配(對(duì)于函數(shù)對(duì)象和類(lèi))和虛擬調(diào)用會(huì)引入運(yùn)行時(shí)間開(kāi)銷(xiāo)。這時(shí)可以通過(guò)內(nèi)聯(lián)函數(shù)消除這類(lèi)的開(kāi)銷(xiāo)。舉個(gè)例子:

fun printName(a: String, name: (str: String) -> String): String {
    var str = "$a${name("Czh")}"
    return str
}

println(printName("Name:", { name -> getName("Czh") }))

上面代碼中,printName函數(shù)有一個(gè)函數(shù)類(lèi)型的參數(shù),通過(guò)Lambda表達(dá)式向printName函數(shù)傳入?yún)?shù)值,Kotlin編譯器會(huì)為L(zhǎng)ambda表達(dá)式單獨(dú)創(chuàng)建一個(gè)對(duì)象,再將Lambda表達(dá)式轉(zhuǎn)換為相應(yīng)的函數(shù)并調(diào)用。如果這種情況出現(xiàn)比較多的時(shí)候,就會(huì)很消耗資源。這是可以在函數(shù)前使用inline關(guān)鍵字,把Lambda函數(shù)內(nèi)聯(lián)到調(diào)用處。如下所示:

inline fun printName(a: String, name: (str: String) -> String): String {
    var str = "$a${name("Czh")}"
    return str
}

println(printName("Name:", { name -> getName("Czh") }))

2.禁用內(nèi)聯(lián)

通過(guò)inline關(guān)鍵字,編譯器將Lambda函數(shù)內(nèi)聯(lián)到調(diào)用處,消除了運(yùn)行時(shí)消耗。但內(nèi)聯(lián)可能導(dǎo)致生成的代碼增加,所以需要避免內(nèi)聯(lián)比較大的Lambda表達(dá)式。如果想禁用一些Lambda函數(shù)的內(nèi)聯(lián),可以使用noinline修飾符禁用該Lambda函數(shù)的內(nèi)聯(lián),如下所示:

inline fun printName(name1: (str1: String) -> String
                     , noinline name2: (str2: String) -> String): String {
    var str = "${name1("Name:")}${name2("Czh")}"
    return str
}

3.內(nèi)聯(lián)屬性

inline關(guān)鍵字除了可以使函數(shù)內(nèi)聯(lián)之外,還能內(nèi)聯(lián)沒(méi)有幕后字段(field)的屬性,如下所示:

val foo: Foo
    inline get() = Foo()

var bar: Bar
    get() = ……
    inline set(v) { …… }

總結(jié)

本篇文章對(duì)比了Java方法和Kotlin函數(shù)在寫(xiě)法上的區(qū)別,也認(rèn)識(shí)了Lambda函數(shù)和還列舉了一些Kotlin函數(shù)中比較特別的語(yǔ)法,如中綴表示法等??梢?jiàn)Kotlin中的函數(shù)內(nèi)容還是很多的,用法也相對(duì)復(fù)雜,但運(yùn)用好Kotlin的函數(shù),能使開(kāi)發(fā)變得更簡(jiǎn)單。

參考文獻(xiàn):
Kotlin語(yǔ)言中文站、《Kotlin程序開(kāi)發(fā)入門(mén)精要》

推薦閱讀:
從Java到Kotlin(一)為什么使用Kotlin
從Java到Kotlin(二)基本語(yǔ)法
從Java到Kotlin(三)類(lèi)和接口
從Java到Kotlin(四)對(duì)象與泛型
從Java到Kotlin(六)擴(kuò)展與委托
從Java到Kotlin(七)反射和注解
從Java到Kotlin(八)Kotlin的其他技術(shù)
Kotlin學(xué)習(xí)資料匯總


更多精彩文章請(qǐng)掃描下方二維碼關(guān)注微信公眾號(hào)"AndroidCzh":這里將長(zhǎng)期為您分享原創(chuàng)文章、Android開(kāi)發(fā)經(jīng)驗(yàn)等!
QQ交流群: 705929135

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容