Kotlin學習筆記——函數

函數的聲明

函數的聲明使用關鍵字fun,格式爲 [訪問限定符] fun 函數名([參數名: 參數類型])[: 返回值類型] {}

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

函數的調用

  • 普通調用
add(1, 2)
  • 通過類調用函數
    通過類的實例調用函數,使用.連接符調用
Math().add(1, 2) // 調用Math類裏面的add函數

函數的參數

函數參數使用 Pascal 表示法定義,即 name: type。參數用逗號隔開。每個參數必須有顯式類型:

fun powerOf(number: Int, exponent: Int) { …… }

參數默認值

函數參數可以有默認值,函數聲明是如果制定了默認值,在調用函數是可以省略相應的參數。與其他語言相比,這可以減少重載數量。函數參數設置默認值的格式是name: type = default_value

fun add(x: Int = 0, y: Int = 0): Int {
    return x + y;
}

// 設置了默認值,函數的調用以下三種都是合法的
println(add())

println(add(1))

println(add(1, 2))
  • 覆蓋方法時自動使用與基類型方法相同的默認參數值。 當覆蓋一個帶有默認參數值的方法時,簽名中必須省略默認參數值:
open class Math {
    open fun add(x: Int, y: Int = 0): Int {
        return x + y
    }
}

class Math2 : Math() {
    // 函數的簽名中不能有默認值設置
    override fun add(x: Int, y: Int): Int {
        return super.add(x, y)
    }
}

Math2().add(1) // 默認使用基類中被重寫函數的默認值設置
  • 如果函數有默認值參數後面,有無默認值的參數,那麼調用時不能省略設置了默認值的參數;
fun add(x: Int = 0, y: Int): Int {
    return x + y
}

println(add(1)) // 錯誤,編譯器江認爲傳入的參數是第一個參數

println(add(1, 2)) // 正確,第一個參數雖然設置了默認值,但是後面還有無默認值的參數,所以第一個參數不能省略
  • 如果在默認參數之後的最後一個參數是 lambda 表達式,那麼它既可以作爲命名參數在括號內傳入,也可以在括號外傳入:


fun add(x: Int, y: Int = 0, qux: () ->Unit): Int {
    return x + y
}

// lambda表達式可以放在括號裏面,也可以放在括號外面
add(1, 2) { println("Add x and y") }
// 默認值後面跟着lambda表達式參數,默認值參數依舊可以省略
add(1, qux = { println("Add x and y") })

命名參數

命名參數,就是在調用函數時使用param_name = param_value的方式制定參數值,這個在默認參數中的作用非常大,可以解決上面提到的“如果默認參數後面存在無默認值的參數情況下不能省略默認參數”的問題,具體可以看以下的例子:

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

Math(y = 2) // 命名參數,指定參數值是y,那麼就可以省略掉本身有默認值的x

命名參數非常有用,假設有一個方法存在許多個默認值參數,然而在調用時,我只需要改變某一個默認值時,使用命名參數,就無需將所有無需改變默認值的參數一一列出來。

fun add(x: Int, y: Int = 0, z: Int = 0, a: Int = 0, b: Int = 0): Int {
    return x + y + z + a + b
}

// 使用命名參數,調用函數時只需要對需要設置值的默認值參數進設置值即可
add(6, z = 2)
add(6, a = 2)
  • 相對於命名參數,直接設置參數值的叫做“位置參數”,在調用函數是,所有的位置參數都必須在命名參數之前;
  • 如果Kotlin調用java,不能使用命名參數,因爲java不支持命名參數。

返回Unit的函數

如果一個函數不返回任何有用的值,它的返回類型是 Unit。這個跟Java中的 void 類似,但是不同的是,Kotlin中這個值不需要顯式返回(默認就是返回 Unit ),而Java的 void 不能省略。

// 不返回任何值,返回值類型是Unit
fun printMsg(msg: String): Unit {
    println(msg)
}

// Unit可以省略,不需要顯式返回
fun sendMsgMsg(msg: String) {
    println(msg)
}

單表達式函數

當函數體返回單個表達式的值時,可以省略花括號並且在 = 符號之後指定表達式即可:

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

顯式返回值類型

除了返回值類型是 Unit 的可以忽略外,其他類型都必須顯示指定返回值類型。Kotlin 不推斷具有塊代碼體的函數的返回類型,因爲這樣的函數在代碼體中可能有複雜的控制流,並且返回類型對於讀者(有時甚至對於編譯器)是不明顯的。

fun add(x: Int, y: Int): Int { // 函數體是代碼塊,必須顯示聲明返回值類型
    val z = x + y
    return z > 0 ? z : 0
}

可變數量的參數

跟Java一樣,函數的參數可以定義成可變數量的,在Kotlin中可變數量參數使用 vararg 關鍵字定義,可變參數實際就是指定類型的一個數組,可以使用訪問數組的方式訪問每個變量。

  • 如果可變參數是函數參數中的最後一個,調用是可直接傳入無數個用逗號隔開的值即可。
fun add(vararg args: Int): Int {
    // args實際就是Array<Int>
    println("Args size: " + args.size)
    var sum = 0
    for (arg in args) { 
        sum += arg
    }
    return sum
}

add(1, 3, 5, 6, 2, 9, 22, 33)
  • 如果函數的參數中存在其他參數(非可變),而且可變參數不是在參數聲明的最後, 可以使用命名參數語法傳遞其後的參數的值,或者,如果參數具有函數類型,則通過在括號外部傳一個 lambda。
fun add(vararg args: Int, tag: String): Int {
    println("Args size: " + args.size)
    var sum = 0
    for (arg in args) {
        sum += arg
    }
    return sum
}

add(1, 2, 3, 4, 5, 6, 7, tag = "XXXX") // 可變參數不是最後一個,可用命名參數實現傳值

中綴表示法

中綴表示法,是忽略該調用的點與圓括號調用的一種表示法,標有 infix 關鍵字的函數可以使用中綴表示法調用。中綴函數必須滿足以下要求:

  • 它們必須是成員函數或擴展函數;
  • 它們必須只有一個參數;
  • 其參數不得接受可變數量的參數且不能有默認值。
class Math {
    fun add(x: Int = 0, y: Int): Int {
        return x + y
    }

    infix fun infixFun(x: Int) {
        println("Param value is $x")
    }
}

// 這兩種寫法等同
Math() infixFun 222
Math().infixFun(222)

  • 需要注意的是,中綴函數總是要求指定接收者與參數。當使用中綴表示法在當前接收者上調用方法時,需要顯式使用 this;不能像常規方法調用那樣省略。這是確保非模糊解析所必需的。
class Math {
    fun add(x: Int = 0, y: Int): Int {
        this infixFun (x + y) // 必須制定接收者與參數,接收者爲當前對象是,必須顯示調用this關鍵字
        return x + y
    }

    infix fun infixFun(x: Int) {
        println("Param value is $x")
    }
}

函數作用域

在 Kotlin 中函數可以在文件頂層聲明,這意味着你不需要像一些語言如 Java、C# 或 Scala 那樣需要創建一個類來保存一個函數。此外除了頂層函數,Kotlin 中函數也可以聲明在局部作用域、作爲成員函數以及擴展函數。

局部函數

Kotlin 支持局部函數,即一個函數在另一個函數內部:

fun sum(vararg args: Int): Int {
    var sum = 0
    // 在Kotlin中,支持在一個函數內部定義一個函數,作用域只在這個函數的內部
    fun add(x: Int, y: Int): Int {
        println("Before calculate the sum is $sum") // 在局部函數內部,可以訪問外部函數中的變量
        return x + y
    }
    
    for ( arg in args) {
        sum = add(sum, arg)
    }
    
    return sum
}

成員函數

成員函數,就是在類內部定義的函數,叫做這個類的成員函數。

class Math {

    fun sum(vararg args: Int): Int {

        var sum = 0
        fun add(x: Int, y: Int): Int {
            println("Before calculate the sum is $sum")
            return x + y
        }
        for ( arg in args) {
            sum = add(sum, arg)
        }

        return sum
    }



    infix fun infixFun(x: Int) {
        println("Param value is $x")
    }
}

泛型函數

在Kotlin中,可以在定義函數的時候定義泛型(在Java中是無法做到的,泛型的定義只能在類聲明中)。

fun <T> log(vararg args: T) {
    for(arg in args) {
        println(arg.toString())
    }
}

高階函數

高階函數是將函數用作參數或返回值的函數。

函數類型

函數類型,是具有與函數簽名(即它們的參數和返回值)相對應的特殊表示法。

  • 所有函數類型都有一個圓括號括起來的參數類型列表以及一個返回類型:(A, B) -> C 表示接受類型分別爲 AB 兩個參數並返回一個 C 類型值的函數類型。 參數類型列表可以爲空,如 () -> AUnit 返回類型不可省略。

  • 函數類型可以有一個額外的接收者類型,它在表示法中的點之前指定: 類型 A.(B) -> C 表示可以在 A 的接收者對象上以一個 B 類型參數來調用並返回一個 C 類型值的函數。 帶有接收者的函數字面值通常與這些類型一起使用。

  • 掛起函數屬於特殊種類的函數類型,它的表示法中有一個 suspend 修飾符 ,例如 suspend () -> Unit 或者 suspend A.(B) -> C

  • 函數類型表示法可以選擇性地包含函數的參數名:(x: Int, y: Int) -> Int。 這些名稱可用於表明參數的含義。

(Int, Int) -> Unit // 返回值是Unit不能省略

() -> Int // 參數列表可以爲空

Math.(Int, Int) -> Int // 函數類型可以指定一個接收者
  • 如需將函數類型指定爲可空,請使用圓括號將整個類型括起來:((Int, Int) -> Int)?
  • 函數類型可以使用圓括號進行接合,這樣可以表示多級的函數類型:(Int) -> ((Int) -> Unit)
  • 箭頭表示法是右結合的(即從右),(Int) -> (Int) -> Unit 與前述示例等價,但不等於 ((Int) -> (Int)) -> Unit
  • 函數類型可以起一個類型別名。

函數類型實例化

函數類型的實例化可以通過集中方法

  • lambda表達式: val f: Math.(Int, Int) -> Int = {x, y -> x + y}
  • 匿名函數:val f: Math.(Int, Int) -> Int = fun (x: Int, y: Int): Int {return x + y}
  • 使用已有聲明的可調用引用(頂層、局部、成員、擴展函數或者屬性):val f: (Int, Int) -> Int = ::add

函數類型實例的調用

函數類型的調用可以通過其 invoke(……) 操作符調用:f.invoke(x) 或者直接 f(x)

println(f.invoke(Math(), 1, 2)) // 如果函數類型有接收者,那麼他的第一個參數必須傳入接收者對象

println(f2.invoke(3, 4))

println(f(Math(), 1, 2)) // 如果函數類型有接收者,那麼他的第一個參數必須傳入接收者對象

println(f2(3, 4))
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章