Kotlin中的高阶函数深入讲解

前言

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

函数类型

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

  • 所有函数类型都有一个圆括号括起来的参数类型列表以及一个返回类型:(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时,就需要针对这些标识进行处理,否则当代码逻辑运行到这些标识处时,就会出现程序的崩溃。

2.run

先给出run函数的源码:

/**
 * 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中的成员变量和成员方法。

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

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

3.with

先看一下with函数的源码:

/**
 * 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的区别,总结一下:

  • 如果泛型T,作为lambda表达式的参数,形如:(T) -> Unit,此时在lambda表示内部使用it;
  • 如果泛型T,作为lambda表达式的接收者,形如:T.() -> Unit,此时在lambda表达式内部使用this;
  • 不论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这几个函数区别不是很明显,有时候使用其中一个函数实现的逻辑,完全也可以用另外一个函数实现,具体使用哪一个,根据个人习惯。需要注意的是:

  • 对作为扩展函数的高阶函数,使用前需要判断接收的对象是否为空,比如T.run,apply,also,let在使用前需要进行空检查;
  • 对于返回对象本身的函数,比如apply,also可以形成链式调用;
  • 对于在函数内部能够使用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

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • Kotlin实现多函数接口的简化调用

    对于一个声明了多个方法的接口,我们使用的时候有时仅仅关注于几个关键方法,并不需要实现所有的.可是由于接口调用的语法限制,使得我们不得不在代码中也显示声明实现了那些我们不关心的方法.在Java中也有简化接口调用的方式,比如安卓中ViewPager监听页面切换时的接口PageChangeListener,官方提供了简单类: ViewPager.SimpleOnPageChangeListener来简化调用. 对于Kotlin来说,可以类似使用java的方式,来实现多函数接口的简化调用,只是要用到ob

  • 玩转Kotlin 彻底弄懂Lambda和高阶函数

    Lambda是什么 简单来讲,Lambda是一种函数的表示方式(言外之意也就是说一个Lambda表达式等于一个函数).更确切的说:Lambda是一个未声明的函数,会以表达式的形式传递 为什么要用Lambda 设想一下,在Android中实现一个View的点击事件,可以使用如下实现: View view = findViewById(R.id.textView); view.setOnClickListener(new View.OnClickListener() { @Override publ

  • Kotlin中常见内联扩展函数的使用方法教程

    前言 Kotlin一个强大之处就在于它的扩展函数,巧妙的运用这些扩展函数可以让你写出的代码更加优雅,阅读起来更加流畅,下面总结了在开发中经常用到的一些内联扩展函数.经常有小伙伴搞不懂with,run,apply等等这些函数该怎么用,在哪里用,我的建议是先记住每个函数的功能(无非就是它需要什么参数?返回值是什么?)记住这两点再根据实际开发中的场景慢慢的就能熟练运用了.其实这些函数极其类似,不同的函数可以完成同样的功能,通过下面的实例也能看出.而在我以往的开发经验中这些函数主要的使用场景有两个,一是

  • 详解Kotlin 高阶函数 与 Lambda 表达式

    详解Kotlin 高阶函数 与 Lambda 表达式 高阶函数(higher-order function)是一种特殊的函数, 它接受函数作为参数, 或者返回一个函数. 这种函数的一个很好的例子就是 lock() 函数, 它的参数是一个锁对象(lock object), 以及另一个函数, 它首先获取锁, 运行对象函数, 然后再释放锁: fun <T> lock(lock: Lock, body: () -> T): T { lock.lock() try { return body()

  • Kotlin教程之函数和包的定义和流程控制

    Kotlin教程之函数和包的定义和流程控制 包名的定义 在xxx.kt文件的顶部进行定义,使用package关键字,定义后,引用包内函数或类,使用包前缀: package cn.jasonmarzw //包定义 fun doSomething(){} // 定义的函数 class User(){} //定义的类 在其他文件中使用时: 可以直接使用 cn.jasonmarzw.doSomething()和 cn.jasonmarzw.User进行调用.可以像Java一样,使用import引入所需要

  • Kotlin扩展函数及实现机制的深入探索

    前言 2017年Google IO大会宣布使用Kotlin作为Android的官方开发语言,相比较与典型的面相对象的JAVA语言,Kotlin作为一种新式的函数式编程语言,也有人称之为Android平台的Swift语言. 先让我们看下实现同样的功能,Java和Kotiln的对比: // JAVA,20多行代码,充斥着findViewById,类型转换,匿名内部类这样的无意义代码 public class MainJavaActivity extends Activity { @Override

  • Kotlin中的扩展函数与属性示例详解

    前言 Kotlin 中类的扩展方法并不是在原类的内部进行拓展,通过反编译为Java代码,可以发现,其原理是使用装饰模式,对源类实例的操作和包装,其实际相当于我们在 Java中定义的工具类方法,并且该工具类方法是使用调用者为第一个参数的,然后在工具方法中操作该调用者: 理论上来说,扩展函数很简单,它就是一个类的成员函数,不过定义在类的外面.让我们来添加一个方法,来计算一个字符串的最后一个字符: package strings /** * @author :Reginer in 2018/5/22

  • Kotlin基础教程之函数定义与变量声明

    Kotlin基础教程之函数定义与变量声明 可以看到,函数定义就是 <访问控制符> <函数名> <参数列表> <:返回类型(不写就是无返回类型)> { 函数体 } 单语句函数可以简写,比如add函数和add1函数效果是一样的 变量定义 var <标识符> : <类型> = <初始化值> 常量定义 val <标识符> : <类型> = <初始化值> 常量与变量都可以没有初始化值,但是在引用前

  • Kotlin中的高阶函数深入讲解

    前言 在Kotlin中,高阶函数是指将一个函数作为另一个函数的参数或者返回值.如果用f(x).g(x)用来表示两个函数,那么高阶函数可以表示为f(g(x)).Kotlin为开发者提供了丰富的高阶函数,比如Standard.kt中的let.with.apply等,_Collectioins.kt中的forEach等.为了能够自如的使用这些高阶函数,我们有必要去了解这些高阶函数的使用方法. 函数类型 在介绍常见高阶函数的使用之前,有必要先了解函数类型,这对我们理解高阶函数很有帮助.Kotlin 使用

  • Swift中的高阶函数功能作用示例详解

    目录 高阶函数的作用 1. 简化代码 2. 提高可读性 3. 支持函数式编程 4. 提高代码的可重用性 常见的高阶函数 1. map() 2. filter() 3. reduce() 4. sorted() 5. forEach() 6. compactMap() 7. flatMap() 8. zip() 9. first() 10. contains() 高阶函数的作用 Swift中的高阶函数是指那些参数或返回值是函数的函数.它们的存在使得我们可以用非常简洁和优雅的代码来解决许多问题. 1

  • Javascript中的高阶函数介绍

    这是一个有趣的东西,这或许也在说明Javascript对象的强大.我们要做的就是在上一篇说到的那样,输出一个Hello,World,而输入的东西是print('Hello')('World'),而这就是所谓的高阶函数. 高阶函数 高阶看上去就像是一种先进的编程技术的一个深奥术语,一开始我看到的时候我也这样认为的. Javascript的高阶函数 然而,高阶函数只是将函数作为参数或返回值的函数.以上面的Hello,World作为一个简单的例子. 复制代码 代码如下: var Moqi = func

  • C#中的高阶函数介绍

    介绍 我们都知道函数是程序中的基本模块,代码段.那高阶函数呢?听起来很好理解吧,就是函数的高阶(级)版本.它怎么高阶了呢?我们来看下它的基本定义: 1:函数自身接受一个或多个函数作为输入 2:函数自身能输出一个函数.  //函数生产函数   满足其中一个就可以称为高阶函数.高阶函数在函数式编程中大量应用.c#在3.0推出Lambda表达式后,也开始慢慢使用了.   目录 1:接受函数 2:输出函数 3:Currying(科里化) 一.接受函数 为了方便理解,都用了自定义. 代码中TakeWhil

  • Kotlin 使用高阶函数实现回调方式

    lambda 和 高阶函数 之前学习了 lambda 和高阶函数,然后在 android 开发中对 onClick 事件进行监听是一个很常用的功能,kotlin 的常规实现如下: rootView.setOnClickListener { view -> println("点击了这个ID=${view.id}的view") } 然后在开发中不可避免的我们也要写一些自定义监听之类的代码.这个时候如果还用 java 的思想去实现的话就有点舍近求远了. java 思想实现 在 java

  • Kotlin中Lambda表达式与高阶函数使用分析讲解

    目录 Lambda表达式 高阶函数 小结 编程语言的发展,通过需求,不断的变化出新的特性,而这些特性就会使得编程变得更加的简洁. Lambda表达式 Lambda表达式的出现,一定程度上使得函数和变量慢慢的融为一体,这样做的好处大大的方便了回调函数的使用. 在很多的情况下,其实我们的函数就只有简单的几行代码,用fun就感觉有点重了,而且有的时候这么大的函数结构用起来,并不是非常的方便. Lambda表达式,其表达式为: {变量定义 -> 代码块} 其中: lambda 函数是一个可以接收任意多个

  • 拥抱kotlin之如何习惯使用kotlin高阶函数

    前言 kotlin提供了高阶函数这个概念,可以在一些场景提高编码效率 一.什么是高阶函数 通俗的说和数学里面的高阶函数概念类似,也就是函数里面的参数可以是函数.当然返回值也可以是函数. 二.kotlin高阶函数使用场景分析 1.先看看平时使用比较多的内置高阶函数 用kotlin写view的onClickListener tV.setOnClickListener { //doSomeThing } 里面的lamba表达式就是一个函数 不太形象?再看看集合里面的filter.map listOf(

  • 详解JavaScript 高阶函数

    高阶函数简介 高阶函数 的英文名叫 Higher-Order Function ,是 函数式编程 中的一种.他的表现形式往往是通过把函数作为参数传入另一个函数,或者将函数作为另一个函数的返回值返回.在实际开发业务中, 高阶函数往往可以抽象我们的代码 ,将我们的命令式编程转换为复用性更高级的函数式编程,从而 提升我们的代码质量 . 下面拿3个面试中常问的高阶函数举例子,希望看完以后能够提升大家对JS的理解,提高我们的代码质量. chat is cheap,show you my code~ Arr

  • 深入浅出的聊聊Swift高阶函数

    目录 初探高阶函数 map compactMap compactMapValues flatMap filter reduce 组合使用 总结 初探高阶函数 在 Swift 中,高阶函数一共有下面几个: map:对给定数组每个元素,执行闭包中的映射,将映射结果放置在数组中返回. flatMap:对给定数组的每个元素,执行闭包中的映射,对映射结果进行合并操作,然后将合并操作后的结果放置在数组中返回. compactMap:对给定数组的每个元素,执行闭包中的映射,将非空的映射结果放置在数组中返回.

随机推荐