從Java到Kotlin(五)

函數與Lambda表達式html

目錄

1、函數聲明與調用
2、參數和返回值
3、單表達式函數
4、函數做用域
5、泛型函數
6、尾遞歸函數
7、中綴表示法
8、Lambda表達式的語法
9、高階函數與Lambda表達式
10、匿名函數
11、內聯函數bash


1、函數聲明與調用

Java 中的方法使用 void 關鍵字聲明:微信

void foo(){}
複製代碼

Kotlin 中的函數使用 fun 關鍵字聲明:閉包

fun foo(){}
複製代碼

用法類似,加入有一個 User 類,裏面有一個 foo() 函數,調用函數的代碼以下: Java代碼ide

new User().foo();
複製代碼

Kotlin代碼函數

User().foo()
複製代碼

2、參數和返回值

聲明有參數的函數,代碼以下: Java代碼post

void foo(String str, int i) {}
複製代碼

Kotlin代碼學習

fun foo(str: String, i: Int) {}
複製代碼

Java先定義類型,後命名;Kotlin先命名,後定義類型,中間用冒號:分隔。二者都是多個參數中間用逗號,分隔。 如函數有返回值,代碼以下: Java代碼優化

String foo(String str, int i) {
    return "";
}
複製代碼

Kotlin代碼ui

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 修飾符標記,表示容許將可變數量的參數傳遞給函數,以下所示:
//用 vararg 修飾符標記參數
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裏全部元素
val list = asList(-1, 0, *a, 4)
//運行代碼,獲得結果爲: [-1, 0, 1, 2, 3, 4]
複製代碼

3、單表達式函數

在Kotlin中,若是函數的函數體只有一條語句,而且有返回值,那麼能夠省略函數體的大括號,變成單表達式函數。以下所示:

//函數體內只有一條語句,且有返回值
fun foo(): String{
    return "abc"
}
//這時能夠省略大括號,變成單表達式函數
fun foo() = "abc"
複製代碼

4、函數做用域

在 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中沒有局部函數這一律念。

5、泛型函數

泛型參數使用尖括號指定,以下所示: 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> {
}
複製代碼

6、尾遞歸函數

尾遞歸函數是一個遞歸函數,用關鍵字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)
複製代碼

再次運行代碼,無堆棧溢出。

7、中綴表示法

中綴表示法是調用函數的另外一種方法。若是要使用中綴表示法,須要用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
複製代碼

8、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 + 1
      val j: Int = i + 3
      val k: Int = it + j - i
      i
      k
      j
}
println(sum(1)) 
//輸出結果爲 5,也就是 j 的值
複製代碼

9、高階函數與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
複製代碼

10、匿名函數

匿名函數與常規函數同樣,只是省略了函數名稱而已。舉個例子

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
複製代碼

11、內聯函數

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: Foo
    inline get() = Foo()

var bar: Bar
    get() = ……
    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

相關文章
相關標籤/搜索