Kotlin中的高阶函数

博客地址sguotao.top/Kotlin-2018…html

在Kotlin中,高阶函数是指将一个函数做为另外一个函数的参数或者返回值。若是用f(x)、g(x)用来表示两个函数,那么高阶函数能够表示为f(g(x))。Kotlin为开发者提供了丰富的高阶函数,好比Standard.kt中的let、with、apply等,_Collectioins.kt中的forEach等。为了可以自如的使用这些高阶函数,咱们有必要去了解这些高阶函数的使用方法。app

函数类型

在介绍常见高阶函数的使用以前,有必要先了解函数类型,这对咱们理解高阶函数颇有帮助。Kotlin 使用相似 (Int) -> String 的一系列函数类型来处理函数的声明,这些类型具备与函数签名相对应的特殊表示法,即它们的参数和返回值:less

  • 全部函数类型都有一个圆括号括起来的参数类型列表以及一个返回类型:(A, B) -> C 表示接受类型分别为 A 与 B 两个参数并返回一个 C类型值的函数类型。参数类型列表能够为空,如 () -> A ,返回值为空,如(A, B) -> Unit;
  • 函数类型能够有一个额外的接收者类型,它在表示法中的点以前指定,如类型 A.(B) -> C 表示能够在 A 的接收者对象上,调用一个以 B 类型做为参数,并返回一个 C 类型值的函数。
  • 还有一种比较特殊的函数类型,挂起函数,它的表示法中有一个 suspend 修饰符 ,例如 suspend () -> Unit 或者 suspend A.(B) -> C 。

经常使用高阶函数

Kotlin提供了不少高阶函数,这里根据这些高阶函数所在文件的位置,分别进行介绍,先来看一下经常使用的高阶函数,这些高阶函数在Standard.kt文件中。函数

1.TODO

先来看一下TODO的源码:学习

/** * Always throws [NotImplementedError] stating that operation is not implemented. */

@kotlin.internal.InlineOnly
public inline fun TODO(): Nothing = throw NotImplementedError()

/** * Always throws [NotImplementedError] stating that operation is not implemented. * * @param reason a string explaining why the implementation is missing. */
@kotlin.internal.InlineOnly
public inline fun TODO(reason: String): Nothing = throw NotImplementedError("An operation is not implemented: $reason")
复制代码

TODO函数有两个重载函数,都会抛出一个NotImplementedError的异常。在Java中,有时会为了保持业务逻辑的连贯性,对未实现的逻辑添加TODO标识,这些标识不进行处理,也不会致使程序的异常,可是在Kotlin中使用TODO时,就须要针对这些标识进行处理,不然当代码逻辑运行到这些标识处时,就会出现程序的崩溃。ui

2.run

先给出run函数的源码:this

/** * Calls the specified function [block] and returns its result. */
@kotlin.internal.InlineOnly
public inline fun <R> run(block: () -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

/** * Calls the specified function [block] with `this` value as its receiver and returns its result. */
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}
复制代码

这两个run函数都接收一个lambda表达式,执行传入的lambda表达式,而且返回lambda表达式的执行结果。区别是T.run()是做为泛型T的一个扩展函数,因此在传入的lambda表达式中可使用this关键字来访问这个泛型T中的成员变量和成员方法。spa

好比,对一个EditText控件,进行一些设置时:ssr

//email 是一个EditText控件
email.run { 
            this.setText("请输入邮箱地址")
            setTextColor(context.getColor(R.color.abc_btn_colored_text_material))
}
复制代码

3.with

先看一下with函数的源码:code

/** * Calls the specified function [block] with the given [receiver] as its receiver and returns its result. */
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}
复制代码

with函数有两个参数,一个类型为泛型T类型的receiver,和一个lambda表达式,这个表达式会做为receiver的扩展函数来执行,而且返回lambda表达式的执行结果。

with函数与T.run函数只是写法上的不一样,好比上面的示例能够用with函数:

with(email, {
            setText("请输入邮箱地址")
            setTextColor(context.getColor(R.color.abc_btn_colored_text_material))
        })
        
        //能够进一步简化为
        with(email) {
            setText("请输入邮箱地址")
            setTextColor(context.getColor(R.color.abc_btn_colored_text_material))
        }
复制代码

4.apply

看一下apply函数的源码:

/** * Calls the specified function [block] with `this` value as its receiver and returns `this` value. */
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}
复制代码

apply函数做为泛型T的扩展函数,接收一个lambda表达式,表达式的receiver是泛型T,没有返回值,apply函数返回泛型T对象自己。能够看到T.run()函数也是接收lambda表达式,可是返回值是lambda表达式的执行结果,这是与apply函数最大的区别。

仍是上面的示例,能够用apply函数:

email.apply { 
            setText("请输入邮箱地址")
        }.apply {
            setTextColor(context.getColor(R.color.abc_btn_colored_text_material))
        }.apply { 
            setOnClickListener { 
                TODO()
            }
        }
复制代码

5.also

看一下also函数的源码:

/** * Calls the specified function [block] with `this` value as its argument and returns `this` value. */
@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
}
复制代码

与apply函数相似,也是做为泛型T的扩展函数,接收一个lambda表达式,lambda表达式没有返回值。also函数也返回泛型T对象自己,不一样的是also函数接收的lambda表达式须要接收一个参数T,因此在lambda表达式内部,可使用it,而apply中只能使用this。

关于this和it的区别,总结一下:

  1. 若是泛型T,做为lambda表达式的参数,形如:(T) -> Unit,此时在lambda表示内部使用it;
  2. 若是泛型T,做为lambda表达式的接收者,形如:T.() -> Unit,此时在lambda表达式内部使用this;
  3. 不论this,仍是it,都表明T对象,区别是it可使用其它的名称代替。

仍是上面的示例,若是用also函数:

email.also { 
            it.setText("请输入邮箱地址")
        }.also { 
            //可使用其它名称
            editView -> editView.setTextColor(applicationContext.getColor(R.color.abc_btn_colored_text_material))
        }.also { 
            it.setOnClickListener { 
                //TODO
            }
        }
复制代码

6.let

看一下let函数的源码:

/** * Calls the specified function [block] with `this` value as its argument and returns its result. */
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}
复制代码

let函数做为泛型T的扩展函数,接收一个lambda表达式,lambda表达式须要接收一个参数T,存在返回值。lambda表达式的返回值就是let函数的返回值。因为lambda表达式接受参数T,因此也能够在其内部使用it。

let应用最多的场景是用来判空,若是上面示例中的EditText是自定义的可空View,那么使用let就很是方便:

var email: EditText? = null
        TODO()
        email?.let { 
            email.setText("请输入邮箱地址")
            email.setTextColor(getColor(R.color.abc_btn_colored_text_material))
        }
复制代码

7.takeIf

看一下takeIf函数的源码:

/** * Returns `this` value if it satisfies the given [predicate] or `null`, if it doesn't. */
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? {
    contract {
        callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
    }
    return if (predicate(this)) this else null
}
复制代码

takeIf函数做为泛型T的扩展函数,接受一个lambda表达式,lambda表达式接收一个参数T,返回Boolean类型,takeIf函数根据接收的lambda表达式的返回值,决定函数的返回值,若是lambda表达式返回true,函数返回T对象自己,若是lambda表达式返回false,函数返回null。

仍是上面的示例,假设用户没有输入邮箱地址,进行信息提示:

email.takeIf { 
            email.text.isEmpty()
        }?.setText("邮箱地址不能为空")
复制代码

8.takeUnless

给出takeUnless函数的源码:

/** * Returns `this` value if it _does not_ satisfy the given [predicate] or `null`, if it does. */
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? {
    contract {
        callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
    }
    return if (!predicate(this)) this else null
}
复制代码

takeUnless函数与takeIf函数相似,惟一的区别是逻辑相反,takeUnless函数根据lambda表达式的返回值决定函数的返回值,若是lambda表达式返回true,函数返回null,若是lambda表达式返回false,函数返回T对象自己。

仍是上面的示例,若是用takeUnless实现,就须要调整一下逻辑:

email.takeUnless {
            email.text.isNotEmpty()  //与takeIf的区别
        }?.setText("邮箱地址不能为空")
复制代码

9.repeat

给出repeat函数的源码:

/** * Executes the given function [action] specified number of [times]. * * A zero-based index of current iteration is passed as a parameter to [action]. */
@kotlin.internal.InlineOnly
public inline fun repeat(times: Int, action: (Int) -> Unit) {
    contract { callsInPlace(action) }

    for (index in 0 until times) {
        action(index)
    }
}
复制代码

repeat函数接收两个参数,一个Int型参数times表示重复次数,一个lambda表达式,lambda表达式接收一个Int型参数,无返回值。repeat函数就是将咱们传入的lambda表达式执行times次。

repeat(3) {
        println("执行第${it + 1}次")
    }
    
    //运行结果
执行第1次
执行第2次
执行第3复制代码

因为repeat函数接收的lambda表达式,须要一个Int型参数,所以在表达式内部使用it,其实it就是for循环的索引,从0开始。

总结

最后对这些高阶函数作一下总结,TODO对比Java中的TODO,须要实现业务逻辑,不能听任不理,不然会出现异常,致使崩溃。takeIf、takeUnless这一对都是根据接收lambda表达式的返回值,决定函数的最终返回值是对象自己,仍是null,区别是takeIf,若是lambda表达式返回true,返回对象自己,不然返回null;takeUnless与takeIf的逻辑正好相反,若是lambda表达式返回true,返回null,不然返回对象自己。repeat函数,见名知意,将接收的lambda表达式重复执行指定次。

run、with、apply、also、let这几个函数区别不是很明显,有时候使用其中一个函数实现的逻辑,彻底也能够用另一个函数实现,具体使用哪个,根据我的习惯。须要注意的是:

  1. 对做为扩展函数的高阶函数,使用前须要判断接收的对象是否为空,好比T.run,apply,also,let在使用前须要进行空检查;
  2. 对于返回对象自己的函数,好比apply,also能够造成链式调用;
  3. 对于在函数内部可以使用it的函数,it能够用意思更加清晰的变量代替,好比T.run,also,let

对这几个函数的区别作一个对比:

函数名称 是否做为扩展函数 是否返回对象自己 在函数内部使用this/ it
run no no -
T.run yes no it
with no no this
apply yes yes this
also yes yes it
let yes no it

学习资料

  1. Kotlin Bootcamp for Programmers
  2. Kotlin Koans
相关文章
相关标签/搜索