Kotlin学习笔记4-1 函数和lambda-函数

函数

Kotlin官网:Functions and Lambdas-Functions

函数声明

使用fun关键字声明函数

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

函数调用

普通的调用方式:

val result = double(2)

.调用:

Sample().foo() // 创建Sample类实例,调用foo函数

参数

函数参数的定义格式为参数名: 类型。
参数名和类型之间用冒号分隔,多个参数之间用逗号分割,参数必须指定类型:

fun powerOf(number: Int, exponent: 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) { ... }  //不允许声明默认值
}

如果带默认值的参数是在其他参数前面,想通过省略参数来使用默认值,需要通过命名参数指定:

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

foo(baz = 1) // bar使用默认值0

函数最后参数为lambda,其他参数有默认值,在括号外声明lambda可以不给函数传参

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

foo(1) { println("hello") } // bar传1,baz使用默认值1
foo { println("hello") }    // bar使用默认值0,baz使用默认值1

命名参数

调用函数时可以通过参数名指定传参,这样可以在多个参数有默认值时指定给哪个参数传参,也可以用来增加可读性。
举个例子:
有下面一个带多个参数和默认值的函数:

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

当只给第一个参数传参,其他参数使用默认值时,reformat(str)没问题。当有多个参数需要传值时,reformat(str, true, true, false, '_'),可读性非常差,如果写成下面的形式:

reformat(str,
    normalizeCase = true,
    upperCaseFirstLetter = true,
    divideByCamelHumps = false,
    wordSeparator = '_'
)

这样可以使代码更易读。
关于使用函数声明顺序传参和使用参数名字传参混用,如下函数:

reformat(str, wordSeparator = '_')

当同时使用参数顺序和参数名字传参时,所有使用顺序的参数必须写在名字传参的前面,例如一个f(x,y)函数,f(1, y = 2)这样写是可以的,1使用0位置给x传参,y=2给名为y的参数传参,而f(x = 1, 2)这样写是不允许的。
对于vararg声明的可变参数,可以使用展开运算符的方式给命名参数传参

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

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

返回Unit

对于没有返回值,类似于Java中void的函数,Kotlin中的返回值是Unit。
返回Unit类型无需显式声明return,函数返回值类型也可以省略。

fun printHello(name: String?): Unit {
    if (name != null)
        println("Hello ${name}")
    else
        println("Hi there!")
    // `return Unit`或`return`可以省略
}
//省略函数返回值类型声明
fun printHello(name: String?) {
    ...
}

单表达式函数

单表达式函数可以省略大括号,直接写成=赋值的形式。返回值类型在可以被编译器推导出时可以省略。

fun double(x: Int): Int = x * 2
//Int型返回值编译器可以通过x*2推导出,此时可以省略
fun double(x: Int) = x * 2

声明返回值类型

对于有函数体的函数,除了Unit类型返回值可以被省略,其他类型都要显式声明,因为函数内部可能非常复杂,编译器无法推导出返回值类型。

可变参数

使用vararg声明,和java中的可变参数类似:

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 list = asList(1, 2, 3)

对于使用者来说,可变参数可以被视为T类型的数组,上例中,ts的类型为Array<out T>
函数参数中只能有一个可变参数。
如果可变参数不是函数的最后一个参数,此时有两种传参方式,一种是可以通过命名参数传参,另一种是,如果可变参数后是一个函数,可以写成括号外lambda的形式。
给可变参数传递的参数也有两种形式,一种是一个一个的传,另一种是通过展开运算符(*数组),这样可以将已有数组的内容传递给可变参数:

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

中缀声明

声明为中缀有以下要求:

  • 成员函数或扩展函数;
  • 一个参数
  • 使用infix关键字标记
// 为Int添加扩展函数
infix fun Int.shl(x: Int): Int {
...
}

//使用中缀声明的扩展函数
1 shl 2

// 等价于
1.shl(2)

作用域

Kotlin中,函数可以直接声明在文件中,可以直接调用,无需包含在类中。也可以声明为一级成员,局部函数,成员函数和扩展函数。

局部函数

函数可以在局部声明,例如函数内:

fun dfs(graph: Graph) {
    fun dfs(current: Vertex, visited: Set<Vertex>) {
        if (!visited.add(current)) return
        for (v in current.neighbors)
            dfs(v, visited)
    }

    dfs(graph.vertices[0], HashSet())
}

局部函数可以访问外部函数作用域内的局部变量。例如上例可以写成:

fun dfs(graph: Graph) {
    val visited = HashSet<Vertex>()
    fun dfs(current: Vertex) {
        if (!visited.add(current)) return
        for (v in current.neighbors)
            dfs(v)
    }

    dfs(graph.vertices[0])
}

成员函数

定义在类或对象中的函数:

class Sample() {
    fun foo() { print("Foo") }
}

调用:

Sample().foo() //先创建Sample类的实例,再调用foo函数

类和继承的知识详见3-1类和继承。

泛型函数

函数参数类型可以是泛型,泛型在函数名前用尖括号定义:

fun <T> singletonList(item: T): List<T> {
    // ...
}

关于泛型的详细说明见3-1泛型

内联函数

详见4-3内联函数

扩展函数

详见3-5扩展

高阶函数和lambda

详见4-2lambda

尾递归

Kotlin支持尾递归,可以用递归的写法替代一些循环,没有栈溢出的风险。
当函数使用railrec修饰,编译器会将递归转换成循环,更快更高效:

tailrec fun findFixPoint(x: Double = 1.0): Double
        = if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))

上例中的函数作用是寻找自身cos值不再变化的数,算法只是不断的cos自己并与自己比较,结果为0.7390851332151607。上例等价于这样写:

private fun findFixPoint(): Double {
    var x = 1.0
    while (true) {
        val y = Math.cos(x)
        if (x == y) return x
        x = y
    }
}

要使用tailrec修饰函数有如下要求

  • 函数内必须在最后调用自己
  • 调用自己后不能再执行其他内容
  • 不能在try/catch/finally中使用
  • 目前仅支持JVM
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值