kotlin之函数

函数的声明

使用fun关键字来声明函数:

fun double(x: Int): Int {
    return 2 * x
}

函数的使用

全局函数(其实就是函数声明所在类的静态方法)的使用:

val result = double(2)

类的成员方法的使用:

Stream().read() // create instance of class Stream and call read()

参数

参数的定义:

fun powerOf(number: Int, exponent: Int): Int { /*...*/ }

默认参数

默认参数能够减少类中重载的方法,默认参数的定义:

fun read(
    b: Array<Byte>,
    off: Int = 0,
    len: Int = b.size,
) { /*...*/ }

对于重写的方法来说,子类所拥有的重写方法拥有与父类一样的默认参数值,子类重写父类中带有默认参数的方法时,方法中必须将参数的默认值省略掉。

open class A {
    open fun foo(i: Int = 10) { /*...*/ }
}

class B : A() {
    override fun foo(i: Int) { /*...*/ }  // No default value is allowed
}

如果一个无默认值的参数位于有默认值参数的前面,那么要想使用默认值,就必须使用具名参数。

fun foo(
    bar: Int = 0,
    baz: Int,
) { /*...*/ }

foo(baz = 1) // The default value bar = 0 is used

如果默认参数后面的最后一个参数是lambda表达式,那么可以使用具名参数或者将lambda表达式位于括号外。

fun foo(
    bar: Int = 0,
    baz: Int = 1,
    qux: () -> Unit,
) { /*...*/ }

foo(1) { println("hello") }     // Uses the default value baz = 1
foo(qux = { println("hello") }) // Uses both default values bar = 0 and baz = 1 
foo { println("hello") }        // Uses both default values bar = 0 and baz = 1

具名参数

在调用函数时,函数参数是可以具名的,当一个函数拥有大量的参数或者拥有一些默认参数时,这种调用方式是比较方便的。

fun reformat(
    str: String,
    normalizeCase: Boolean = true,
    upperCaseFirstLetter: Boolean = true,
    divideByCamelHumps: Boolean = false,
    wordSeparator: Char = ' ',
) {
/*...*/
}

如果同时使用了位置参数和具名参数,那么前面必须是相连的位置参数,后面必须是相连的具名参数。

reformat('This is a short String!', upperCaseFirstLetter = false, wordSeparator = '_')

可变参数可以借助为spread operator以具名参数的方式传递:

fun foo(vararg strings: String) { /*...*/ }

foo(strings = *arrayOf("a", "b", "c"))

在kotlin中调用java方法时不能使用具名参数,因为java的字节码中并不总是保留方法的参数名。

返回Unit的函数

如果函数没有返回值,可以返回Unit来代表没有返回值:

fun printHello(name: String?): Unit {
    if (name != null)
        println("Hello $name")
    else
        println("Hi there!")
    // `return Unit` or `return` is optional
}

Unit返回类型可以省略:

fun printHello(name: String?) { ... }

单表达式函数

如果一个函数只会返回一个简单的表达式,那么花括号都可以省略:

fun double(x: Int): Int = x * 2

返回值也无需指定,编译器能够自动推导出来:

fun double(x: Int) = x * 2

显示返回类型

有方法体的函数必须显示的指定返回类型,除非方法返回Unit才可以省略,kotlin不会去自动推导有方法体的函数,因为这些方法通常拥有很复杂的业务流程,返回值对阅读代码的人来说就不那么明显了,有时候,对于编译器来说同样如此。

可变参数

方法的参数可以用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
}

一个方法中只允许一个参数为vararg,通常作为最后一个参数,如果不是最后一个参数,那么后面的参数必须使用具名参数来调用,如果最后一个参数是一个函数类型,还可以通过圆括号外声明lambda表达式来调用。

val list = asList(1, 2, 3)

val a = arrayOf(1, 2, 3)
val list = asList(-1, 0, *a, 4)

中缀符号

函数还可以通过中缀符号来调用,函数需要满足以下条件:

  1. 必须是成员函数或者扩展函数
  2. 函数必须只有一个参数
  3. 函数不能是可变参数或者有默认值
infix fun Int.add(other: Int) = this + other

fun main() {
    println(5 add 6)
}

尾递归

kotlin中,如果某个函数的末尾又调用了函数自身,这种就称为尾递归函数。尾递归函数需要在fun前面添加tailrec。

尾递归函数会使用循环的方式替代递归,从而避免栈溢出。尾递归不能在异常处理的try、 catch 、 finally块中使用。

例如:计算阶乘的函数。

fun fact(n: Int): Int {
    return if (n == 1) {
        1
    } else {
        n * fact(n - 1)
    }
}

上面函数将调用自身作为其执行体的最后一行代码,且递归调用后没有更多代码,因此可以将该函数改为尾递归语法。此时,上面函数可改为如下形式

tailrec fun factRec(n: Int, total : Int= 1): Int = if (n == 1) total else factRec(n - 1 , total * n)

与普通递归相比,编译器会对尾递归进行修改,将其优化成一个快速而高效的基于循环的版本,这样就可以减少可能对内存的消耗。