从Java到Kotlin(五)
函數與Lambda表達式
目錄
一、函數聲明與調用
二、參數和返回值
三、單表達式函數
四、函數作用域
五、泛型函數
六、尾遞歸函數
七、中綴表示法
八、Lambda表達式的語法
九、高階函數與Lambda表達式
十、匿名函數
十一、內聯函數
一、函數聲明與調用
Java 中的方法使用 void 關鍵字聲明:
void foo(){} 復制代碼Kotlin 中的函數使用 fun 關鍵字聲明:
fun foo(){} 復制代碼用法相似,加入有一個 User 類,里面有一個 foo() 函數,調用函數的代碼如下: Java代碼
new User().foo(); 復制代碼Kotlin代碼
User().foo() 復制代碼二、參數和返回值
聲明有參數的函數,代碼如下: Java代碼
void foo(String str, int i) {} 復制代碼Kotlin代碼
fun foo(str: String, i: Int) {} 復制代碼Java先定義類型,后命名;Kotlin先命名,后定義類型,中間用冒號:分隔。兩者都是多個參數中間用逗號,分隔。 如函數有返回值,代碼如下: Java代碼
String foo(String str, int i) {return ""; } 復制代碼Kotlin代碼
fun foo(str: String, i: Int): String {return "" } 復制代碼Java是把void替換成返回值的類型,而Kotlin是把返回值聲明在函數的末尾,并用冒號:分隔。 兩種語言聲明參數和返回值的方式有點相似,而Kotlin還有更強大的功能,例如默認參數和 命名參數,如下所示: 函數參數可以有默認值,當沒有給參數指定值的時候,使用默認值
//給i指定默認值為1 fun foo(str: String, i: Int = 1) {println("$str $i") } //調用該函數,這個時候可以只傳一個參數 foo("abc") //運行代碼,得到結果為: abc 1 復制代碼如果有默認值的參數在無默認值的參數之前,要略過有默認值的參數去給無默認值的參數指定值,要使用命名參數來指定值,有點繞我們看代碼:
//有默認值的參數在無默認值的參數之前 fun foo(i: Int = 1, str: String) {println("$str $i") } //foo("hello") //編譯錯誤 foo(str = "hello") //編譯通過,要使用參數的命名來指定值 //運行代碼,得到結果為: hello 1 復制代碼- 可變數量的參數
函數的參數可以用 vararg 修飾符標記,表示允許將可變數量的參數傳遞給函數,如下所示:
三、單表達式函數
在Kotlin中,如果函數的函數體只有一條語句,并且有返回值,那么可以省略函數體的大括號,變成單表達式函數。如下所示:
//函數體內只有一條語句,且有返回值 fun foo(): String{return "abc" } //這時可以省略大括號,變成單表達式函數 fun foo() = "abc" 復制代碼四、函數作用域
在 Kotlin 中函數可以在文件頂層聲明,這意味著你不需要像一些語言如 Java 那樣創建一個類來保存一個函數。此外除了頂層函數,Kotlin 中函數也可以聲明在局部作用域、作為成員函數以及擴展函數。
1. 成員函數
成員函數是指在類或對象里定義的函數。
Java代碼:
class User {//在類里定義函數。void foo() {} } //調用 new User().foo(); 復制代碼Kotlin代碼:
class User() {//在類里定義函數。fun foo() {} } //調用 User().foo() 復制代碼2. 局部函數
Kotlin支持在函數內嵌套另一個函數,嵌套在里面的函數成為局部函數,如下所示:
fun foo() {println("outside")fun inside() {println("inside")}inside() }//調用foo()函數 foo() 復制代碼運行代碼,得到結果
而Java中沒有局部函數這一概念。五、泛型函數
泛型參數使用尖括號指定,如下所示: 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> { } 復制代碼六、尾遞歸函數
尾遞歸函數是一個遞歸函數,用關鍵字tailrec來修飾,函數必須將其自身調用作為它執行的最后一個操作。當一個函數用tailrec修飾符標記并滿足所需的形式時,編譯器會優化該遞歸,留下一個快速而高效的基于循環的版本,無堆棧溢出的風險,舉個例子: 先看一段代碼
fun count(x: Int = 1): Int = if (x == 10) x else count(x - 1) 復制代碼上面的count()函數是一個死循環,當我們調用count()函數后,會報StackOverflowError。這時可以用tailrec修飾符標記該遞歸函數,并將其自身調用作為它執行的最后一個操作,如下所示:
tailrec fun count(x: Int = 1): Int = if (x == 10) x else count(x - 1) 復制代碼再次運行代碼,無堆棧溢出。
七、中綴表示法
中綴表示法是調用函數的另一種方法。如果要使用中綴表示法,需要用infix 關鍵字來修飾函數,且要滿足下列條件:
- 它們必須是成員函數或擴展函數;
- 它們必須只有一個參數;
- 其參數不得接受可變數量的參數。
下面來舉個例子:
//擴展函數 infix fun String.removeLetter(str: String): String {//this指調用者return this.replace(str, "") }//調用 var str = "hello world" //不使用中綴表示法 println(str.removeLetter("h")) //輸出ello world //使用中綴表示法 println(str removeLetter "d") //輸出hello worl //使用中綴表示法調用str removeLetter "d"等同于調用str.removeLetter("d")//還可以連續調用 println(str.removeLetter("h").removeLetter("d").removeLetter("l")) // 輸出 eo wor println(str removeLetter "h" removeLetter "d" removeLetter "l") // 輸出 eo wor 復制代碼八、Lambda表達式的語法
Lambda表達式的語法如下:
- Lambda 表達式總是括在大括號中;
- 其參數(如果有的話)在 -> 之前聲明(參數類型可以省略);
- 函數體(如果存在的話)在 -> 后面。
舉個例子:
//這是一個Lambda表達式的完整語法形式 val sum = { x: Int, y: Int -> x + y } //Lambda表達式在大括號中 //參數 x 和 y 在 -> 之前聲明 //參數聲明放在大括號內,并有參數類型標注 //函數體 x + y 在 -> 后面val i: Int = sum(1, 2) println(i) //輸出結果為 3 復制代碼如果Lambda表達式自動推斷的返回類型不是Unit,那么在Lambda表達式函數體中,會把最后一條表達式的值當做是返回值。所以上面的常量sum 的返回值是Int類型。如果要指定常量sum的返回值為Int類型,可以這樣寫:
val sum: (Int, Int) -> Int = { x, y -> x + y }val i: Int = sum(1, 2) println(i) //輸出結果為 3 復制代碼當Lambda表達式只有一個參數的時候,那么它將可以省略這個唯一的參數的定義,連同->也可以省略。如下所示:
//當Lambda表達式只有一個參數的時候 val getInt: (Int) -> Int = { x -> x + 1 } val int = getInt(2) println(int) //輸出結果為:3//可以省略這個參數的定義 //并且將隱含地獎這個參數命名為 it val sum: (Int) -> Int = { it + 1 } val int = sum(2) println(int) //輸出結果為:3 復制代碼上面說到如果Lambda表達式自動推斷的返回類型不是Unit,那么在Lambda表達式函數體中,會把最后一條表達式的值當做是返回值。舉個例子:
var sum: (Int) -> Int = {val i: Int = it + 1val j: Int = i + 3val k: Int = it + j - iikj } println(sum(1)) //輸出結果為 5,也就是 j 的值 復制代碼九、高階函數與Lambda表達式
高階函數是將函數用作參數或返回值的函數,如下所示:
fun getName(name: String): String {return name }fun printName(a: String, name: (str: String) -> String): String {var str = "$a${name("Czh")}"return str }//調用 println(printName("Name:", ::getName)) //運行代碼,輸出 Name:Czh 復制代碼上面代碼中name: (str: String) -> String是一個函數,擁有函數類型() -> String,接收一個String參數,當我們執行var str = "$a${name("Czh")}"這行代碼的時候,相當于執行了var str = "$a${getName("Czh")}",并返回了字符串"Czh"。當我們調用printName("Name:", ::getName)時,將函數作為參數傳入高階函數,需要在該函數前加兩個冒號::作為標記。
Kotlin提供了Lambda表達式來讓我們更方便地傳遞函數參數值。Lambda表達式總是被大括號括著;如果有參數的話,其參數在 -> 之前聲明,參數類型可以省略;如果存在函數體的話,函數體在-> 后面,如下所示:
println(printName("Name:", { name -> getName("Czh") })) //運行代碼,輸出 Name:Czh 復制代碼如果函數的最后一個參數是一個函數,并且你傳遞一個Lambda表達 式作為相應的參數,你可以在圓括號()之外指定它,如下所示:
println(printName("Name:") { name -> getName("Czh") }) //運行代碼,輸出 Name:Czh 復制代碼十、匿名函數
匿名函數與常規函數一樣,只是省略了函數名稱而已。舉個例子
fun(x: Int, y: Int): Int = x + y 復制代碼匿名函數函數體是表達式,也可以是代碼段,如下所示:
fun(x: Int, y: Int): Int {return x + y } 復制代碼上面高階函數的例子中的printName函數的第二個參數也可以傳入一個匿名函數,如下所示:
println(printName("Name:", fun(str: String): String { return "Czh" })) //運行代碼,輸出 Name:Czh 復制代碼十一、內聯函數
1.內聯函數
使用高階函數會帶來一些運行時的效率損失。每一個函數都是一個對象,并且會捕獲一個閉包。 即那些在函數體內會訪問到的變量。 內存分配(對于函數對象和類)和虛擬調用會引入運行時間開銷。這時可以通過內聯函數消除這類的開銷。舉個例子:
fun printName(a: String, name: (str: String) -> String): String {var str = "$a${name("Czh")}"return str }println(printName("Name:", { name -> getName("Czh") })) 復制代碼上面代碼中,printName函數有一個函數類型的參數,通過Lambda表達式向printName函數傳入參數值,Kotlin編譯器會為Lambda表達式單獨創建一個對象,再將Lambda表達式轉換為相應的函數并調用。如果這種情況出現比較多的時候,就會很消耗資源。這是可以在函數前使用inline關鍵字,把Lambda函數內聯到調用處。如下所示:
inline fun printName(a: String, name: (str: String) -> String): String {var str = "$a${name("Czh")}"return str }println(printName("Name:", { name -> getName("Czh") })) 復制代碼2.禁用內聯
通過inline關鍵字,編譯器將Lambda函數內聯到調用處,消除了運行時消耗。但內聯可能導致生成的代碼增加,所以需要避免內聯比較大的Lambda表達式。如果想禁用一些Lambda函數的內聯,可以使用noinline修飾符禁用該Lambda函數的內聯,如下所示:
inline fun printName(name1: (str1: String) -> String, noinline name2: (str2: String) -> String): String {var str = "${name1("Name:")}${name2("Czh")}"return str } 復制代碼3.內聯屬性
inline關鍵字除了可以使函數內聯之外,還能內聯沒有幕后字段(field)的屬性,如下所示:
val foo: Fooinline get() = Foo()var bar: Barget() = ……inline set(v) { …… } 復制代碼總結
本篇文章對比了Java方法和Kotlin函數在寫法上的區別,也認識了Lambda函數和還列舉了一些Kotlin函數中比較特別的語法,如中綴表示法等。可見Kotlin中的函數內容還是很多的,用法也相對復雜,但運用好Kotlin的函數,能使開發變得更簡單。
參考文獻:
Kotlin語言中文站、《Kotlin程序開發入門精要》
推薦閱讀:
從Java到Kotlin(一)為什么使用Kotlin
從Java到Kotlin(二)基本語法
從Java到Kotlin(三)類和接口
從Java到Kotlin(四)對象與泛型
從Java到Kotlin(五)函數與Lambda表達式
從Java到Kotlin(六)擴展與委托
從Java到Kotlin(七)反射和注解
從Java到Kotlin(八)Kotlin的其他技術
Kotlin學習資料總匯
更多精彩文章請掃描下方二維碼關注微信公眾號"AndroidCzh":這里將長期為您分享原創文章、Android開發經驗等! QQ交流群: 705929135
總結
以上是生活随笔為你收集整理的从Java到Kotlin(五)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 让delphi程序不受WINDOWS日期
- 下一篇: PHP-redis中文文档