定义函数

函数有两个Int类型的参数和Int类型的返回值。

fun sum(a : Int, b : Int) : Int{
    return a + b;
}

如果函数只包含一个表达式,而且它的返回值是可推断的。

fun sum(a : Int, b : Int) = a + b

函数返回无意义的值,使用关键字Unit。

fun printMsg(a : Int, b : Int) : Unit{
    println("msg of $a and $b is ${a + b}")
}

Unit关键字可以取消。

fun printMsg(a : Int, b : Int){
    println("msg of $a and $b is ${a + b}")
}

函数如果存在方法体,及多个表达式时,必须显示的指定返回类型。当然如果返回的Unit时也可以不指定。

可变参数函数

局部函数

成员函数

泛型函数

inline函数

noinline函数

如果想让指定的lambda表达式inline,另一些不inline,可以使用noinline表达式。

inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {}

inlinelambda表达式必须在inline函数或传递inline的参数中进行调用。但是noinline函数没有这些限制,可以按照喜欢的方式进行调用。

使用时需要注意,如果一个inline函数有一个noinline参数函数和具体化的类型参数,编译器会产生一个警告,因为在这种情况下极有可能达不到inline的效果。

return语句

Kotlin中,我们可以使用return直接退出函数调用,但是退出lambda表达式,我们需要使用一个标签,并且一个单独的return是禁止的,因为lambda不能让闭包函数返回。

fun foo() {
    ordinaryFunction {
        return // 错误
    }
}

如果传入的lambda表达式是inline的,return也会被inline,所以以下方式是允许的。

fun foo() {
    inlineFunction {
        return // 正确:lambda表达式inlined
    }
}

以上的这种return方式被称为non-local returns。

inline函数在调用lambda表达式时可能将其作为参数而不是直接调用,而是从另外的上下文环境执行,比如本地变量或嵌套函数。在这种情况下,non-local returns是不被允许的。另外需要注意lambda参数需要使用crossinline标记。

inline fun f(crossinline body: () -> Unit) {
    val f = object: Runnable {
        override fun run() = body()
    }
}

break和continue现在还不支持,正在计划中。

具体化的类型参数

有时候我们需要访问传递给我们的参数类型。

fun <T> TreeNode.findParentOfType(clazz: Class<T>): T? {
    var p = parent
    while (p != null && !clazz.isInstance(p)) {
        p = p?.parent
    }
    @Suppress("UNCHECKED_CAST")
    return p as T
}

上面的函数检查参数是否是特定的类型,虽然能使用,但是调用不优雅。

myTree.findParentOfType(MyTreeNodeType::class.java)

我们想要的仅仅是传递一个类型给函数,如下:

findParentOfType<MyTreeNodeType>()

为了实现这个,inline函数支持reified类型参数

inline fun <reified T> TreeNode.findParentOfType(): T? {
    var p = parent
    while (p != null && p !is T) {
        p = p?.parent
    }
    return p as T
}

使用reified限制类型参数,然后就可以在函数内部访问了。就像它是一个正常的类。因为函数是inline的,就不需要反射了,取而代之是使用!isas。普通函数(没有标记为inline)不能使用reified参数,不能再运行时保留的类型不能作为reified参数。

inline属性

inline修饰符可以用在属性存储器上(没有支持字段),你可以标记一个单独的属性存储器。

val foo: Foo
    inline get() = Foo()
var bar: Bar
    get() = ...
    inline set(v) { ... }

可以标记所有的属性,让其标记为inline

inline var bar: Bar
    get() = ...
    set(v) { ... }

在调用点,内联读写方法作为常规的内联函数内联。

扩展函数请看这里。

高阶函数和lambda表达式

函数可以作为参数或返回值,这种函数被称为高阶函数。

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

上述代码中:() -> T是函数类型,代表无参数的函数并且返回值是T,它会在try-block中执行。

调用lock()方法,我们可以传递另一个函数作为参数。

fun toBeSynchronized() = sharedResource.operation()
val result = lock(lock, ::toBeSynchronized)

或者使用lambda表达式:

val result = lock(lock, { sharedResource.operation() })

为了能够是内容继续下去,简单介绍一下lambda表达式:

  • lambda表达式总是定义在大括号内
  • 它的参数(如果有的话)被定义在->之前,参数类型可以忽略。
  • 主体部分在->之后

在Kotlin中,如果一个函数的最后一个参数是函数,你可以传入一个lambda表达式,你可以在调用前指定它。

高阶函数中的map()函数:

fun <T, R> List<T>.map(transform: (T) -> R): List<R> {
    val result = arrayListOf<R>()
    for (item in this)
        result.add(transform(item))
    return result
}
val doubled = ints.map { value -> value * 2 }

从上述例子中可以看出,如果lambda表达式是仅有的参数,那么调用函数的括号是可以去掉的。

it是单个参数的隐式名字,如果函数只有一个参数,它的定义(包括->)可以取消。

ints.map { it * 2 }

另一个例子

strings.filter { it.length == 5 }.sortBy { it }.map { it.toUpperCase() }

如果lambda中的参数未被使用,你可以使用下划线替换它的名字。

map.forEach { _, value -> println("$value!") }

lambda表达式和匿名函数

max(strings, { a, b -> a.length < b.length })

max函数的第二个参数将lambda表达式的值作为参数。在传入的第二个参数中,其本身就是一个函数,其等价于:

fun compare(a: String, b: String): Boolean = a.length < b.length

函数类型

为了在一个函数中接受另一个函数作为参数,我们需要指定函数的类型以满足调用函数的参数要求。比如上面所述的max原型为:

fun <T> max(collection: Collection<T>, less: (T, T) -> Boolean): T? {
    var max: T? = null
    for (it in collection)
        if (max == null || less(max, it))
            max = it
    return max
}

less参数的类型为:(T, T) -> Boolean,意思是参数类型为两个参数的函数并且返回Boolean。

在代码的第四行,less被用作函数进行调用。

函数类型可以按照上述方式书写,也可以有其他的名字,如果你想提供更有意义的名字。

val compare: (x: T, y: T) -> Int = ...

Lambda语法

lambda表达式的全写形式如下:

val sum = { x: Int, y: Int -> x + y }

lambda表达式总是在大括号中,参数在圆括号中并且可以有额外的注解,主体在->之后。如果返回类型不为Unit,那么lambda表达式的最后一个表达式将作为返回值。如果去掉额外的注解信息,那么它的定义看起来将会是下面的样子:

val sum: (Int, Int) -> Int = { x, y -> x + y }

lambda表达式只有一个参数是很常见的,如果Kotlin可以在函数外指定签名,那么将允许我们不定义这个参数,并且会隐式的定义这个参数,它的名字叫it。

ints.filter { it > 0 } // 类型 '(it: Int) -> Boolean',可以计算出来

显示的返回值可以使用return语句或是lambda表达式的最后一个表达式,所以下面两种方式是等价的。

ints.filter {
    val shouldFilter = it > 0
    shouldFilter
}
ints.filter {
    filer@ val shouldFilter = it > 0
    return@filter shouldFilter
}

匿名函数

在上述的lambda表达式中,缺少了返回值类型的内容。在大多数情况下,这是非必须的。因为返回类型可以推断出来。然而,如果你需要显示的指定,你可以使用匿名函数。

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

匿名函数没有名字,它的主体部分可以是单个表达式或者方法块。

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

匿名函数中的返回类型的指定与其他函数一直,但是如果参数类型可以推断出来,那么参数类型可以忽略。

ints.filter(fun(item) = item > 0)

匿名函数的返回类型可以根据表达式主体推断或是显示的指定。

需要注意的是匿名函数的参数总是需要传入的,只有lambda表达式的参数在特定情况下可以忽略。

另外一个与lambda表达式不同的地方是:没有使用标签的return语句将会返回匿名函数本身,而在lambda表达式中,它将会结束函数调用。

Closures(闭包)

lambda表达式或匿名函数以访问它的闭包。比如:外部范围定义的变量。与Java不同的是,闭包中的变量是可以修改的。

var sum = 0
ints.filter { it > 0 }.forEach {
    sum += it
}
print(sum)

函数文本接收器

Kotlin提供一种特殊的接收对象,叫做函数文本(function literals),在函数文本的内部,可以访问接收者的成员;类似于扩展函数,在函数体中访问接收者的成员。

函数文本的类型就是接收者的类型:

sum : Int.(other: Int) -> Int

函数文本可以调用就像它是接收者的一个方法。

1.sum(2)

也可以使用匿名函数方式:

val sum = fun Int.(other: Int): Int = this + other

使用Lambda表达式方式:

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
}

尾递归函数

results matching ""

    No results matching ""