Java转Kotlin:高阶函数

140 阅读4分钟

1 介绍

1.1 定义

1.2 常用高阶函数

More details see scope functions

let() / run()

  • Declaration
/**
 * Calls the specified function [block] with `this` value as its argument and returns its result.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#let).
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

/**
 * Calls the specified function [block] with `this` value as its receiver and returns its result.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#run).
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

let() has a functional type of arg (T)->R, and it returns R.

run() has a functional type of arg T.()->R, and it returns R.

  • Invoke
class Person(var name: String, var age: Int){
    override fun toString(): String {
        return "${this.name} is ${this.age} years old."
    }
}

fun main() {
    var person0 = Person("Pony", 24)
    println(person0)
    val person1 = person0.let {
        it.name = "Jack"
        it.age = 55
        it
    }
    println(person0)
    println(person1)
    val person2 = person1.run {
        this.name = "Robin"
        this.age = 50
        this
    }
    println(person1)
    println(person2)
}

Output in console:

In conclusion:

  • Similarity:

let() and run() are almost the same, they both can reach the object which invoke them and they both return the result of lambda expression;

  • Contrast:

let() treats the object as an argment of the block, and reaches the object by using it;

run() treats the object as a receiver, and reaches the object by using this or even this can be omitted.

  • So:

It is recommended by the official that when the block aims mainly at operating the object, run() should be choosen, otherwise let().

also() / apply()

  • Declaration
/**
 * Calls the specified function [block] with `this` value as its argument and returns `this` value.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#also).
 */
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.also(block: (T) -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block(this)
    return this
}

/**
 * Calls the specified function [block] with `this` value as its receiver and returns `this` value.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#apply).
 */
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

also() has a functional type of arg (T)->Unit and returns T; apply() has a functional type of arg T.()->Unit and returns T;

  • Invoke
class Person(var name: String, var age: Int){
    override fun toString(): String {
        return "${this.name} is ${this.age} years old."
    }

    fun selfIntroduction(){
        println(this)
    }
}

fun main() {
    val person = Person("Pony", 50)
    person.also {
        println(it.name)
        println(it.age)
    }.selfIntroduction()
    person.apply {
        this.name = "Jack"
        this.age = 55
    }.selfIntroduction()
}

Output in console:

In conclusion:

  • Similarity:

also() and apply() are almost the same, they both can reach the object which invoke them and they both return the object itself;

  • Contrast:

also() treats the object as an argment of the block, and reaches the object by using it;

apply() treats the object as a receiver, and reaches the object by using this or even this can be omitted.

  • So:

It is recommended by the official that when the block aims mainly at operating the object, apply() should be choosen, otherwise also().

use()

  • Declaration
/**
 * Executes the given [block] function on this resource and then closes it down correctly whether an exception
 * is thrown or not.
 *
 * @param block a function to process this [Closeable] resource.
 * @return the result of [block] function invoked on this resource.
 */
@InlineOnly
@RequireKotlin("1.2", versionKind = RequireKotlinVersionKind.COMPILER_VERSION, message = "Requires newer compiler version to be inlined correctly.")
public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {
    var exception: Throwable? = null
    try {
        return block(this)
    } catch (e: Throwable) {
        exception = e
        throw e
    } finally {
        when {
            apiVersionIsAtLeast(1, 1, 0) -> this.closeFinally(exception)
            this == null -> {}
            exception == null -> close()
            else ->
                try {
                    close()
                } catch (closeException: Throwable) {
                    // cause.addSuppressed(closeException) // ignored here
                }
        }
    }
}

use() is defined for Closeable, which needs to be closed after been used. Such as io stream etc.

use() has a functional type of arg (T)->R where T must be a Closeable?, and returns R.

  • Invoke
fun main() {
    File("build.gradle")
        .inputStream()
        .reader()
        .buffered()
        .use {
            it.readLines().forEach { eachLine ->
                println(eachLine)
            }
        }
}

Output in console:

In conclusion:

use() is used to close those Closeable? automatically.

1.3 高阶函数的调用

对于forEach()高阶函数,常规调用应该写作:

如果函数类型的参数是最后一个传入参数,那么在调用高阶函数的时候,这个函数类型的参数可以连大括号一起,写到小括号外面去

如果函数类型的参数是唯一一个传入参数,那么在调用的时候,函数类型的参数已经写到小括号外面了,小括号里面什么都没有,可以去掉,而且推荐去掉,这为后面的DSL语法打下了基础。

2 实战

2.1 计时函数

fun cost(block: () -> Unit) {
    val start: Long = System.currentTimeMillis()
    block()
    println("${System.currentTimeMillis() - start} ms")
}

这个函数很好理解,不赘述。

2.2 斐波拉契函数

fun fiboracci(): () -> Long {
    var first = 0L
    var second = 1L
    return {
        val next = first + second
        val current = first
        first = second
        second = next
        current
    }
}

这个斐波拉契函数并不是最终用于计算斐波拉契数列的函数,其输出的lambda表达式才是重要的被调用者,这个斐波拉契函数只是给定了斐波拉契数列的前两个值,仅此而已。

2.3 主函数

fun main() {
    cost {
        val fiboracciNext = fiboracci()
        for (i in 0..10) {
            println(fiboracciNext())
        }
    }
}

主函数中打印的始终是lambda表达式的输出,即最后一行的值,也就是程序中变量current的值。

2.4 控制台输出

小结