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

目录
  • 正文
    • 1.函数类型
    • 2.高阶函数
    • 3.系统标准高阶函数
    • 4.Lambda表达式

正文

Kotlin中函数都是头等的,这意味着它可以存储在变量与数据结构中、作为参数传递给其他高阶函数以及从其他高阶函数返回。可以向操作任何其他非函数值一样操作函数。

为促成这点,作为一门静态类型编程语言的Kotlin使用一系列函数类型来表示函数并提供了一组专门的语言结构,例如Lambda表达式

这段话来自Kotlin文档。从没有接触过Kotlin的话这段话的意思很难理解,上面有三个关键词【高阶函数】、【函数类型】、【Lambda表达式】先分析这三个关键词然后再对上面这段话进行理解。

1.函数类型

函数类型就是函数的类型, 变量有类型IntString等,那函数的类型到底是指什么?

fun lastElement(str: String): Char {
    return str[str.length - 1]
}

上面的代码是之前用过的,意思是获取字符串的最后一个字符,其中参数类型是String,返回值类型是Char,将其抽出来就是【String -> Char】这就代表了函数的类型,一句话概括就是:将函数的【参数类型】和【返回值类型】抽象出来就得到了函数的【函数类型】。【(String) -> Char】的意思就是参数类型是【String】,返回值类型是【Char】的函数类型。这个比较好理解。类似的还有Kotlin中继承自BaseAdapter的几个方法

//函数类型:() -> Int
override fun getCount(): Int {
}
//函数类型:(Int) -> Any
override fun getItem(position: Int): Any {
}
//函数类型:(Int) -> Long
override fun getItemId(position: Int): Long {
}

2.高阶函数

理解饿了函数类型再来看下高阶函数。

高阶函数是将函数用作参数或返回值的函数, 这是高阶函数的定义。

  • 函数用作参数的高阶函数写法
fun main() {
    val result = answer(20) { 10 }
    println("result:$result")       //输出结果:result:30
}
/**
 * 高阶函数
 * 函数类型:(Int, add方法) -> Int
 */
fun answer(num: Int, add: () -> Int): Int {
    return num + add()
}

上面的代码用的是高阶函数中的函数用作参数的写法,定义一个answer方法,添加一个参数num和函数add,因为add方法返回值是一个Int类型因此可以跟num直接相加并返回结果,代码没有实际意义就是个例子

这里可能会产生一个疑问:为什么result的调用方式是成立的?

将上面的代码转换成Java的写法就清楚了

public final class HighFunctionKt {
   public static final void main() {
      int result = answer(20, (Function0)null.INSTANCE);
      String var1 = "result:" + result;
      boolean var2 = false;
      System.out.println(var1);
   }
   // $FF: synthetic method
   public static void main(String[] var0) {
      main();
   }
   public static final int answer(int num, @NotNull Function0 add) {
      Intrinsics.checkNotNullParameter(add, "add");
      return num + ((Number)add.invoke()).intValue();
   }
}

可以看到add方法被转换成了Function0,并且num与add方法的返回值进行了相加,那么这里有一个新的疑问invoke是什么?invokeFunction0的一个方法,作用就是调用函数,在Functions.kt类中,Function的数量达到Function21,从Function0Function21的区别就是传参数量的不同。

public interface Function0<out R> : Function<R> {
    /** Invokes the function. */
    public operator fun invoke(): R
}

再来看一个稍微复杂的高阶函数例子,answer函数中又添加了一个参数,同时函数参数也支持传参

fun main() {
    val add = answer(10, 20) { num1, num2 -> num1 + num2 }
    val minus = answer(10, 20) { num1, num2 -> num1 - num2 }
    println("add:$add")       //输出结果:result:30
    println("minus:$minus")       //输出结果:result:30
}
fun answer(num1: Int, num2: Int, result: (Int, Int) -> Int): Int {
    return result(num1, num2)
}

answer方法做了改动,传了两个参数,函数类型的参数也传入了两个参数,这样定义的作用是更灵活

  • 函数用作返回值的高阶函数写法
fun main() {
    println(answer(10).invoke())	//输出结果:输入的数:10
}
fun answer(num: Int): () -> String {
    return { "输入的数:${num}" }
}

这里的invoke就是一个调用函数的功能。编译成Java代码如下所示:

public final class HighFunctionKt {
    public static final void main() {
        Object var0 = answer(10).invoke();
        boolean var1 = false;
        System.out.println(var0);
    }
    // $FF: synthetic method
    public static void main(String[] var0) {
        main();
    }
    @NotNull
    public static final Function0 answer(final int num) {
        return (Function0)(new Function0() {
            // $FF: synthetic method
            // $FF: bridge method
            public Object invoke() {
                return this.invoke();
            }
            @NotNull
            public final String invoke() {
                return "输入的数:" + num;
            }
        });
    }
}
  • 应用场景举例

Android开发过程中RecycleView是常用的一个组件,但是它本身不支持点击事件,现在,假设我们在Adapter中有两个点击事件,添加和删除,常用的写法会先定义一个接口,对该接口定义一个变量,然后定义一个方法,代码如下:

private lateinit var mOnItemAddClickListener: OnItemAddClickListener
private lateinit var mOnItemDeleteClickListener: OnItemDeleteClickListener
interface OnItemAddClickListener {
    fun onItemAddClick(position: Int)
}
interface OnItemDeleteClickListener {
    fun onItemDeleteClick(position: Int)
}
fun setOnItemAddClickListener(onItemAddClickListener: OnItemAddClickListener) {
    mOnItemAddClickListener = onItemAddClickListener
}
fun setOnItemDeleteClickListener(onItemDeleteClickListener: OnItemDeleteClickListener) {
    mOnItemDeleteClickListener = onItemDeleteClickListener
}
holder.ivAdd.setOnClickListener {
    mOnItemAddClickListener.onItemAddClick(position)
}
holder.ivDelete.setOnClickListener {
    mOnItemDeleteClickListener.onItemDeleteClick(position)
}
adapter.setOnItemAddClickListener(object :DemoAdapter.OnItemAddClickListener{
    override fun onItemAddClick(position: Int) {
        TODO("Not yet implemented")
    }
})
adapter.setOnItemDeleteClickListener(object :DemoAdapter.OnItemDeleteClickListener{
    override fun onItemDeleteClick(position: Int) {
        TODO("Not yet implemented")
    }
})

用高阶函数对其进行优化后的代码如下:

private lateinit var mOnItemAddClickListener: (Int) -> Unit
private lateinit var mOnItemDeleteClickListener: (Int) -> Unit
fun setOnItemAddClickListener(listener: (Int) -> Unit) {
    mOnItemAddClickListener = listener
}
fun setOnItemDeleteClickListener(listener: (Int) -> Unit) {
    mOnItemDeleteClickListener = listener
}
holder.ivAdd.setOnClickListener {
    mOnItemAddClickListener.invoke(position)
}
holder.ivDelete.setOnClickListener {
    mOnItemDeleteClickListener.invoke(position)
}
adapter.setOnItemAddClickListener {
}
adapter.setOnItemDeleteClickListener {
}

这两种写法的代码进行对比可以发现高阶函数的实现方式中没有定义接口,同时它代码量显著减少,代码也变得更加简洁。

3.系统标准高阶函数

系统的标准高阶函数来自Standard.kt,里面的方法也是比较常用的

  • run
public inline fun <R> run(block: () -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

block是函数式参数的名称,传入的函数类型是()-> RR是泛型

这段代码的意思就是调用传入的函数并返回结果,return block()就是传入函数的调用。

怎么用?or有什么用?

fun main() {
    run { println(add(1, 2)) }
}
fun add(num1: Int, num2: Int): Int {
    return num1 + num2
}

作用就是构建Lambda更方便

  • T.run
public inline fun <T, R> T.run(block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

这个run更上面那个的区别在于它有一个接收者,有了这个接收者就可以使用上下文了,例如:

val num = 10
num.run { println("result:${this + 1}") }	//输出结果:result:11

还有一种情况,例如要从某个对象中取出它的一些属性的值也可以通过T.run,同时由于可以将this省略因此代码就可以这么写:

class Person(val name:String, var age:Int)
val person = Person("张三", 19)
person.run{
    println("name:$name")
    println("age:$age")
}

再举个例子,TextView利用T.run修改属性并赋值

holder.tvText.run {
    text = "自定义文本"
    setTextColor(context.getColor(R.color.color_000000))
    textSize = 20F
}
  • whith
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}

with的使用方式与T.run类似,with在返回值上带了一个接收者,看下面代码

with(holder.tvText) {
    text = "自定义文本"
    setTextColor(context.getColor(R.color.color_000000))
    textSize = 20F
}
  • T.apply
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

这个函数的意思就是接收者传入了什么返回值就是什么,使用案例:

val person = Person("张三", 19)
person.apply {
    println("name:$name")			//输出结果:张三
    println("age:$age")				//输出结果:19
}.age = 10					   		//把张三的年龄修改我10岁
println("name:${person.name}")		//输出结果:张三
println("age:${person.age}")		//输出结果:10

有什么用?

Android中会遇到根据状态修改Button样式然后还要响应点击事件的情况,常用写法就不在这里讲了,这里用T.apply实现:

button.apply {
    text = "提交"
    setTextColor(context.getColor(R.color.color_000000))
    background = context.getDrawable(R.drawable.shape_solid_4dp_4e6cf5)
}.setOnClickListener {
    //点击事件
}

这行代码是不是很简洁。

还有webview的使用

webView.apply {
    settings.javaScriptEnabled = true
    settings.useWideViewPort = true
}.loadUrl("https://www.baidu.com/")

T.apply很灵活,具体问题具体分析就好。

  • T.also
public inline fun <T> T.also(block: (T) -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block(this)
    return this
}

T.also的意思就是用传参调用指定函数并返回这个参数

button.also {
    it.text
}

这里的button在调用also之后在它的里面只能用it也必须用it,这个it指的是button本身,而T.apply中是this指的是Button本身,并且这个this是可以被省略的。使用过程中的区别不大。

  • T.let
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

T.let的意思就是使用传参调用指定函数然后返回结果,代码如下

val person = Person("张三", 19)
val str = "Hello Kotlin"
val result = person.let {
    it.name == "张三"
}
println("result:$result")		//输出结果:result:true
println("length:${str.let { it.length }}")	//输出结果:length:12

在Person类中有一个name = 张三的实例,经过let判断后返回true;获取str字符串的长度得到最终结果12。

  • T.takeIf
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? {
    contract {
        callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
    }
    return if (predicate(this)) this else null
}

T.takeIf的意思就是如果符合条件则返回传入的值,否则返回null

val person = Person("张三", 19)
val result = person.takeIf {
    it.name == "李四"
}
println("result:${result?.name}")		//输出结果:result:null
val result = person.takeIf {
    it.name == "张三"
}										//条件成立返回person对象
  • T.takeUnless
public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? {
    contract {
        callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
    }
    return if (!predicate(this)) this else null
}

T.takeUnlessT.takeIf正好相反,如果满足条件则返回null,不满足则返回正确的谓词

val person = Person("张三", 19)
val result = person.takeUnless {
    it.name == "李四"
}
println("result:${result?.name}")		//输出结果:result:张三
val result = person.takeUnless {
    it.name == "张三"
}
println("result:${result?.name}")		//输出结果:result:null
  • repeat
public inline fun repeat(times: Int, action: (Int) -> Unit) {
    contract { callsInPlace(action) }
    for (index in 0 until times) {
        action(index)
    }
}

action函数就是一个重复执行的函数,从0开始

repeat(2) {
    println("执行第:${it}次")
}
//输出结果:
//执行第:0次
//执行第:1次

4.Lambda表达式

Lambda表达式在Java中已经用的比较多了,通过它可以简化代码和提高开发效率,所以我们可以把Lambda表达式理解为函数的简写

例如view的点击事件在Kotlin中调用时就是这样的:

fun setOnClickListener(l: ((View!) -> Unit)?){
}
//调用时可以这么写:
button.setOnClickListener{
}

开头提出了一个结论:在Kotlin中函数是头等的,为什么这么说呢?

  • Kotlin的函数可以独立于类之外,这就是顶层函数
  • Kotlin的函数可以作为参数也可以作为函数,它被称为高阶函数和Lambda
  • Kotlin的函数可以向变量一样,这叫做函数引用

以上就是Kotlin 高阶函数与Lambda表达式示例详解的详细内容,更多关于Kotlin 高阶函数Lambda的资料请关注我们其它相关文章!

(0)

相关推荐

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

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

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

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

  • Kotlin高阶函数reduce与fold使用实例

    目录 reduce示例 fold示例 源码解析 总结 在 Kotlin 中,reduce() 和 fold() 是函数式编程中常用的高阶函数.它们都是对集合中的元素进行聚合操作的函数,将一个集合中的元素缩减成一个单独的值.它们的使用方式非常相似,但是返回值略有不同.下面是它们的区别: reduce() 函数是对集合中的所有元素进行聚合处理,并返回最后一个合并处理值. fold() 函数除了合并所有元素之外,还可以接受一个初始值,并将其与聚合结果一起返回.注:如果集合为空的话,只会返回初始值. r

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

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

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

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

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

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

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

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

  • Kotlin协程的基础与使用示例详解

    目录 一.协程概述 1.概念 2.特点 3.原理 1)续体传递 2)状态机 二.协程基础 1.协程的上下文 2.协程的作用域 3.协程调度器 4.协程的启动模式 5.协程的生命周期 1)协程状态的转换 2)状态标识的变化 三.协程使用 1.协程的启动 1)runBlocking方法 2)launch方法 3)async方法 4)suspend关键字 5)withContext方法 6)suspend方法 2.协程间通信 1)Channel 2)Channel的容量 3)produce方法与act

  • JavaScript数组对象高阶函数reduce的妙用详解

    目录 reduce 是 JavaScript 数组对象上的一个高阶函数 计算数组的平均数 求数组的最大值 求数组的最小值 数组去重 计算数组中每个元素出现的次数 实现数组分组 计算数组中连续递增数字的长度 计算对象数组的属性总和 将对象数组转换为键值对对象 计算数组中出现次数最多的元素 实现 Promise 串行执行 对象属性值求和 按属性对数组分组 扁平化数组 合并对象 reduce 是 JavaScript 数组对象上的一个高阶函数 它可以用来迭代数组中的所有元素,并返回一个单一的值. 其常

  • Java 8 lambda表达式引入详解及实例

    Java 8 lambda表达式引入详解及实例 eclipse 下载安装 Help -> EclipseMarketplace -> 搜索Java 8 Kepler ->Java 8 support for eclipse Kepler SR2 安装完成后需要重启 Android Studio 在project的build.gradle文件中添加 buildscript { dependencies { classpath 'me.tatarka:gradle-retrolambda:3

  • ThinkPHP Where 条件中常用表达式示例(详解)

    Where 条件表达式格式为: $map['字段名'] = array('表达式', '操作条件'); 其中 $map 是一个普通的数组变量,可以根据自己需求而命名.上述格式中的表达式实际是运算符的意义: ThinkPHP运算符 与 SQL运算符 对照表 TP运算符 SQL运算符 例子 实际查询条件 eq = $map['id'] = array('eq',100); 等效于:$map['id'] = 100; neq != $map['id'] = array('neq',100); id !

  • React 高阶组件与Render Props优缺点详解

    目录 高阶组件 增强型高级组件 注入型高阶组件 高阶组件 VS Render Props 总结 高阶组件 高阶组件(HOC)是一个接受组件作为参数并返回一个新组件的函数,如果多个组件有相同的逻辑,将这些逻辑用函数封装,使它们能跨组件共用,这种用法称为高阶组件.下面的代码演示什么是高阶组件: export default function WithPrintLog(InnerComponent) { return class extends React.Component{ componentDi

  • Java8 新特性Lambda表达式实例详解

    Java8 新特性Lambda表达式实例详解 在介绍Lambda表达式之前,我们先来看只有单个方法的Interface(通常我们称之为回调接口): public interface OnClickListener { void onClick(View v); } 我们是这样使用它的: button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { v.setText("

  • 对python实现二维函数高次拟合的示例详解

    在参加"数据挖掘"比赛中遇到了关于函数高次拟合的问题,然后就整理了一下源码,以便后期的学习与改进. 在本次"数据挖掘"比赛中感觉收获最大的还是对于神经网络的认识,在接近一周的时间里,研究了进40种神经网络模型,虽然在持续一周的挖掘比赛把自己折磨的惨不忍睹,但是收获颇丰.现在想想也挺欣慰自己在这段时间里接受新知识的能力.关于神经网络方面的理解会在后续博文中补充(刚提交完论文,还没来得及整理),先分享一下高次拟合方面的知识. # coding=utf-8 import

  • python函数传参意义示例详解

    目录 C++这样的语言用多了之后,在Python函数传递参数的时候,经常会遇到一个问题,我要传递一个引用怎么办? 比如我们想要传一个x到函数中做个运算改变x的值: def change(y): y += 1 x = 1 print ("before change:", x) change(x) print ("after change: ", x) 得到的结果是 before change: 1 after change:  1 完全没用~~~这是怎么回事? 我来说

随机推荐