Kotlin Reference (十五) 函数和lambda表达式:普通函数和高阶函数

KotLin 相关文档


官方在线Reference
kotlin-docs.pdf
Kotlin for android Developers 中文翻译
Kotlin开发工具集成,相关平台支持指南
Kotlin开源项目与Libraries
Kotlin开源项目、资源、书籍及课程搜索平台
Google’s sample projects written in Kotlin
Kotlin and Android

 

函数


Kotlin中的函数,必须以fun关键字来定义;函数参数,必须声明类型,以:来分隔
如下,test函数,参数为一个 名为a的Int型变量:

fun test(a: Int) {
}

使用infix关键字,中缀表达式

使用infix关键字,特征:

  • 可用在扩展函数、成员函数前

  • 参数只有一个

在后续调用时, 可以形如:x <funName> y

如对Int,扩展一个名为shll的函数:

infix fun Int.shll(x: Int): Int {
    return this shl x
}

调用:

1 shll 2 //<==> 1.shll(2)

 

含默认值的参数列表

参数必须显示声明类型

含默认值的参数列表,调用时可以省略 传入有默认值的参数;以减少函数重载数量
(当然,如果三个以上参数,中间有参数有默认值,后面参数没有,调用时是不能省略传参的)

如,

fun read(a: Int, b: Int = 0, c: Int = 0) {    
}

调用如下,

read(1)       //省略b、c
read(1, 2)    //省略c
read(1, 2, 3) 

子类重写

基类 open一个含默认值参数列表的函数,子类要override:子类重写函数不能有默认值参数

如,

open class A {
    open fun foo(i: Int = 10) {  }
}
class B : A() {
    override fun foo(i: Int) {  } // no default value allowed
}

 

命名参数

命名参数(named arguments),即调用时,带上参数名,格式:参数名=参数值

如,

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

调用如下,

reformat("") //省略默认参数
reformat(str = "",
        normalizeCase = true,
        upperCaseFirstLetter = true,
        divideByCamelHumps = false,
        wordSeparator = '_')
reformat(str = "", wordSeparator = '_')

注意:如果前一个参数是命名参数,后一个就不能是非命名的;命名参数的调用名称,与对应参数的声明时名称需要一致

优点:增加可读性;有多个带默认值的参数时,还可以在调用时,跳过中间的参数

 

返回Unit

返回值为Unit:相当于返回一个空对象;可以在函数声明中,省略返回值定义;也可以在函数实现中,省略返回值语句

如,

fun printHello(name: String?): Unit {// <==> fun printHello(name: String?)
    if (name != null) {
        return Unit
    } else {
        //省略返回语句
    }
}

 

##单个函数表达式
当函数仅返回一个表达式,可以省略函数主体(函数块)声明,形如 fun a(p: Type) = b

如,

fun doubleMultiply(x: Int): Int = x * 2 // <==> fun double(x: Int) = x * 2

 

可变参数

可变参数(Variable number of arguments),java中使用...,kotlin中使用 vararg关键字

与java类似,kotlin中的可变参数对象,也是一个数组,即 Array类型

如,

fun <T> asList(vararg ts: T): List<T> {
    val result = ArrayList<T>()
    result.addAll(ts) // ts is an Array
    return result
}

调用,

asList(1, 2, 3)

声明在其它参数前

可变参数只能有一个,如果声明在其它参数前:系统无法自动推断,可以使用命名参数语法进行调用。

将一个可变参数,传递给另一个函数中的可变参数,要在可变参数前使用*符号

如,再有一个函数asList2:

fun <T> asList2(vararg ts: T, a: Int): List<T> {
    return asList(*ts)
}

调用,

asList2(ts = arrayOf(1, 2, 3), a = 4)

参数列表中有函数类型的参数

  • 函数型参数,在可变参数前

如,

fun <T> asList3(op: () -> Unit, vararg ts: T): List<T> {
    return asList(*ts)
}

调用,

asList3({

}, 1, 2)

注:调用时,书写不好看,不推荐;推荐后一种声明形式

  • 函数型参数,在可变参数后

如,

fun <T> asList4(vararg ts: T, op: () -> Unit): List<T> {
    return asList(*ts)
}

调用,

asList4(1, 2, op = {

})
 
asList4(1, 2) {

}

注:调用时,函数类型写在参数内,需要使用命名参数语法;
或直接写到参数外(推荐这样的调用形式)

 

函数范围

函数可以声明在一个顶级的文件中,即kotlin-file中;

可以声明成一个类的成员函数、类的扩展函数;

可声明成一个局部函数,即函数中声明的函数;局部函数可以访问外部函数中的局部变量

 

尾递归函数

满足尾递归形式的函数(不一定要是函数表达式),使用 tailrec关键字,

编译器中会优化出一个更有效率的循环语句替代,防止栈溢出。

注意:在 try/catch/finally 语句块中,进行尾递归调用,tailrec 无法优化

如,

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

上面的递归,相当于如下循环:

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

 

高阶函数和lambda表达式


一个函数声明中,将另一个函数作为参数或返回值,这样的函数就是高阶函数(Higher-Order Functions)。

如,

fun <T> lock(lock: Lock, body: () -> T): T { 
	lock.lock()
	try {
		return body()
	} finally {
		lock.unlock() 
	}
}

如上,函数参数body的类型是一个函数,书写使用 lambda 表达式:() -> T,表明这个函数中没有参数,返回类型T。在try块中直接调用body函数:body()

调用,如前文可变参数中所述:可以在参数列表中使用命名参数语法,或在参数列表外加函数体

lock(ReentrantLock(), body = {})
lock(ReentrantLock()) {}

 

使用函数(方法)引用

在传递函数参数时,可以传递另一个函数签名相同的函数,这时就可以使用函数引用

如,

fun <T> usFun(a: Int, body: (arg: Int) -> T): T {//body中,也可声明成 (Int) -> T
    return body(a)
}
fun suffix(arg: Int): String {
    return "${arg}_suffix"
}

上面suffix函数签名,可以用于代入到usFun中的body参数上。

调用,

val result =  usFun(3, body = { arg -> suffix(arg) })//命名参数
val resultX =  usFun(4) {arg -> suffix(arg)} //一般lambda
val resultXX = usFun(5, ::suffix) //lambda中的方法引用

 

_Collections.kt中的map、forEach函数

_Collections.kt中的map函数,可以看成类似如下实现:

fun <T, R> List<T>.mapz(transform: (T) -> R): List<R> {
    val result = arrayListOf<R>()
    for (item in this)
        result.add(transform(item))
    return result
}

调用

listOf(1, 2).mapz { i -> i * 2f }.forEach { println(it) }

这里只是为了演示,一般就直接调用 map函数即可

当函数型参数,其内部只有一个参数时,可使用it来指代该参数,而不使用声明成: p -> … 这样的形式

 

函数型参数内有不使用参数

函数型参数,它的其中有不使用的参数,可以用_声明

mapOf(Pair("a", 1), Pair("b", 2)).forEach { t, u -> println("t=$t, u=$u") } //两个参数,都被使用了

mapOf(Pair("a", 1), Pair("b", 2)).forEach { _, value -> println("$value!") }

mapOf(Pair("a", 1), Pair("b", 2)).forEach { _, _ -> println("3!") }

 

属性为函数型

//val vv = println("aa") //声明一个 类型为函数形的属性
val vv: (Any?) -> Unit = ::println //声明一个 类型为函数形的属性
fun aa() = vv
aa()

上面两种声明 vv的方式,是等价的。
 

匿名函数代替函数型参数

listOf(3, 4).filter { it > 0 }
listOf(3, 4).filter(fun(item) = item > 0) //fun(item)为匿名函数

 

字面函数和接收者

字面函数和接收者(Function Literals with Receiver)

如下:

val sum = fun Int.(other: Int): Int = this + other
val sum2 = fun Int.(other: Int): Int {
    return this + other
}

上面就像是类的扩展函数的 表达式写法。
sum实际上是一个函数,调用:

1.sum(3)

再例:

class HTML {
    fun body() { }
}
fun html(init: HTML.() -> Unit): HTML {
    val html = HTML() // create the receiver object
    html.init() // pass the receiver object to the lambda
    return html
}

调用:

html { // lambda with receiver begins here
     body() // calling a method on the receiver object
}

fun html() 参数是一个 HTML 的函数: () -> Unit, 用 init 接收;
html.init() 调用 init 指代的函数,即是传入的 body();

 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值