高阶函数与labmda表达式

2024-09-02 13:58
文章标签 函数 表达式 高阶 labmda

本文主要是介绍高阶函数与labmda表达式,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

提纲

在这里插入图片描述

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

其实理解一个新东西,定义是特别重要的,上述定义是kotlin官方文档给出的,它描述了高阶函数是什么,接受了这个定义就不会畏难了。通过上述定义知道高阶函数和函数的区别是输入参数可以是函数,返回值也可以是函数。

组成

函数由输入输出组成

输入
  • 普通类型实例
  • 函数类型实例
输出
  • 普通类型实例
  • 函数类型实例
函数类型:定义一个函数
  • (A,B) -> C:用普通函数定义,输入为A,B输出为C
  • A.(B) -> C:用扩展函数定义
  • 挂起函数:用特殊类型的函数定义
  • 别名:给声明的函数起名
一个典型的高阶函数
// 定义一个高阶函数
fun <T, R> Collection<T>.fold(initial: R,// 普通函数定义的函数类型combine: (acc: R, nextElement: T) -> R
): R {var accumulator: R = initialfor (element: T in this) {combine.invoke(accumulator, element)accumulator = combine(accumulator, element)}return accumulator
}
// 简单调用该高阶函数// Lambdas 表达式是花括号括起来的代码块。
val items = listOf(1, 2, 3, 4, 5)
items.fold(0, // 这是之后将要谈到的labmda表达式,作用类似普通对象的new即取实例化函数类型{// 如果一个 lambda 表达式有参数,前面是参数,后跟“->”acc: Int, i: Int ->print("acc = $acc, i = $i, ")val result = acc + iprintln("result = $result")// lambda 表达式中的最后一个表达式是返回值:result
})
函数类型的实例化

类似普通对象实例化的new关键字或者各种依赖注入形式的实例化比如工厂模式,注解等实例化方式,函数类型也由很多方式取实例化

lambda表达式
  • 结构:参数+函数体{x: Int, y: Int -> x + y}
  • 实例声明:val sum = {x: Int, y: Int -> x + y} vs val sum = Int(2)
  • 实例调用:print(sum(1, 2))
  • 作用:更方便的实例化函数类型,往往利用它进行更好的封装
  • demo:
fun max(v1: String, v2: String, comparetor: (String, String) -> Boolean): Boolean {return comparetor.invoke(v1, v2)}
fun testLambda() {// 其实和普通对象的实例并无本质上的区别max("123", "234", { a: String, b: String ->a.length < b.length})// lambda表达式可以写在末尾max("123", "234") { a: String, b: String ->a.length < b.length}// Kotlin的类型推断:String类型可以推断max("123", "234") { a, b ->a.length < b.length}// 用lambda编码的形式声明一个函数类型的实例val compare = { a: String, b: String -> a.length < b.length }max("123", "234", compare)}
匿名函数: 没有名字函数
  • 结构:顾名思义,如下:
 fun(param: Int): String {return param.toString()}

注意,这个不像函数可以直接声明,因为它是一个值,一个实例而不是一个声明。

  • 声明
val toString = fun(param: Int): String {return param.toString()}

我们可以简单认为toString就是该匿名函数名称, 但是其实编译后的java代码并不是如此。

  • 调用:和简单的函数一样的调用方式toString(1)
  • 作用:函数类型的实例化
  • demo: 这个demo大家可以比较一下和labmda表达式在格式上的区别
 // 比较匿名函数与labmda表达式的区别
fun testNmhs() {// labmda表达式实例化函数类型max("123", "234") { a: String, b: String ->a.length < b.length}// 匿名函数实例化函数类型max("123", "234", fun(a: String, b: String): Boolean {return a.length < b.length})// 等号定义函数max("123", "234", fun(a: String, b: String) = a.length < b.length)// 同样可以类型推断 max("123", "234", fun(a, b) = a.length < b.length)
}

其实除了格式上的区别,引入匿名函数还有一个重要的原因在于返回的范围,简单来讲匿名函数可以实现局部返回而lambda表达式实现局部返回就比较麻烦,下面的例子实现了从列表中找到一个名为Alice的人后就推出循环,也用它来说明什么叫局部返回什么叫非局部返回。

// 换成foreach
fun lookForAlice(people: List<Person>) {// 局部返回方案// forEach 接受一个lambda函数people.forEach { person ->if (person.name == "Alice") {println("Found")// 这里的局部返回需要带标签return@forEach}}println("Alice is not found")// 匿名函数局部返回println("匿名函数实验开始")people.forEach(fun(person) {if (person.name == "Alice") {println("Found")// 这里的局部返回不用带标签return}println("Alice is not found")})println("匿名函数实验结束")// forEach 接受一个lambda函数people.forEach { person ->if (person.name == "Alice") {println("Found")// 后面会讲到,内联+lambda可以实现非局部返回即结束了整个函数return}}println("Alice is not found")
}

下面我们来看第三种函数类型实例化的方式::

使用已有声明的可调用引用即用::操作符:
  • 结构:(类::成员)如Person::age, Person::isAudit
  • 声明:和所有的实例化方式一样都是可以赋值给一个变量的
    val age = Person::age
  • 调用:调用就像传值一样简单
  • 作用:更简明的一个调用单个方法或访问单个属性的函数值
  • demo
val log = listOf(SiteVisit("/", 34.0, OS.WINDOWS),SiteVisit("/", 22.0, OS.MAC),SiteVisit("/login", 12.0, OS.WINDOWS),SiteVisit(" / signup", 8.0, OS.IOS),SiteVisit("/", 16.3, OS.ANDROID))
data class SiteVisit(val path: String, val duration: Double, val os: OS)
enum class OS { WINDOWS, LINUX, MAC, IOS, ANDROID }
fun testSiteLog() {log.filter { it.os == OS.WINDOWS }.map { it.duration }.average()val time1 = log.filter { it.os == OS.WINDOWS }// 这里直接调用了属性,我们知道duration属性在kotlin中式由get,set方法的这里其实是传了一个get()函数,get()是一个输入是SiteVisit输出是Int函数,可以理解为对(A,B) -> C类型的实例化.map(SiteVisit::duration).average()val time2 = log.averageDurationFor {it.os == OS.ANDROID}}

另外提纲里面提到的因为不常用就不一一解释了。下面我们来看内联函数

内联函数
定义:

对加了inline关键字的函数成为内联函数

作用:

在谈具体作用前需要解释函数类型实例化在kotlin中的具体原理,其实lambda函数等实例化函数类型的方式都会被编译成一个匿名类,即每调用一次该函数实例就会有一个额外的类被创建,这会带来不小的开销,所以内联函数出现了,它的作用如下所述:

在函数被使用的时候编译器并不会生成函数调用的代码,而是使用函数实现的真实代码替换每一次的函数调用

运作方式:
  • 不使用内联:
fun <T> synchronized(lock: Lock, action: () -> T) : T {lock.lock()try {return action()} finally {lock.unlock()}
}
fun main() {synchronized(lock) {println("synchronized inline test")}
}// 编译后的java代码如下public static final Object synchronized(@NotNull Lock lock, @NotNull Function0 action) {Intrinsics.checkParameterIsNotNull(lock, "lock");Intrinsics.checkParameterIsNotNull(action, "action");lock.lock();Object var2;try {var2 = action.invoke();} finally {lock.unlock();}return var2;}
public static final void main() {//实例化了一个Fuction0synchronized((Lock)lock, (Function0)null.INSTANCE);
}

我们可以看到其实例化了一个类Function0

  • 使用内联
 public static final void main() {// 我们可以看到synchronized内部的代码直接被提到了main函数内部int $i$f$synchronized = false;((Lock)lock).lock();boolean $i$f$runUnderLock;try {$i$f$runUnderLock = false;String var3 = "synchronized inline test";boolean var4 = false;System.out.println(var3);Unit var8 = Unit.INSTANCE;} finally {((Lock)lock).unlock();}}

我们可以看到synchronized内部的代码直接被提到了main函数内部

这其实就是内联函数的核心。
当然kotlin也会提供局部使用内联的方式:

禁用内联: 这个特性很少用到
  • 关键字:noinline
  • 作用:被添加noinline关键字的函数类型不会被提到调用者的内部
  • demo:
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) { …… }
具体化类型参数
  • 关键字:reified
  • 作用:消除类型擦除,可以直接用模板访问比如实例instance is T
  • 用法:该关键字只能添加到内联函数的泛型上
inline fun <reified T> TreeNode.findParentOfType(): T? {var p = parent// 如果没有reified关键子,因为类型擦除的原因,参数是无法使用is的,while (p != null && p !is T) {p = p.parent}return p as T?
}
  • 原理

其实还是基于内联函数的性质,内联函数中的代码都会被提到调用者的作用域,所以在编译期其实就知道泛型T是什么类型,所以上述代码编译后的java代码如下

public static void main() {TreeNode p$iv;// 这里的instanceOf MyTreeNode印证了上述表诉for(p$iv = $this$findParentOfType$iv.getParent(); p$iv != null && !(p$iv instanceof MyTreeNode); p$iv = p$iv.getParent()) {}
}
总结:
  • 函数类型可以让你定义一个具有输入,输出的普通函数模板。
  • 高阶函数是以函数类型作为参数或者返回值的函数
  • 函数类型的实例化方式由labmda表达式,匿名函数和::等方式
  • labmda表达式和匿名函数的两个区别是编码方式和返回作用域
  • 内联函数可以消除调用函数类型实例带来的性能开销

这篇关于高阶函数与labmda表达式的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1130148

相关文章

Kotlin 作用域函数apply、let、run、with、also使用指南

《Kotlin作用域函数apply、let、run、with、also使用指南》在Kotlin开发中,作用域函数(ScopeFunctions)是一组能让代码更简洁、更函数式的高阶函数,本文将... 目录一、引言:为什么需要作用域函数?二、作用域函China编程数详解1. apply:对象配置的 “流式构建器”最

Android Kotlin 高阶函数详解及其在协程中的应用小结

《AndroidKotlin高阶函数详解及其在协程中的应用小结》高阶函数是Kotlin中的一个重要特性,它能够将函数作为一等公民(First-ClassCitizen),使得代码更加简洁、灵活和可... 目录1. 引言2. 什么是高阶函数?3. 高阶函数的基础用法3.1 传递函数作为参数3.2 Lambda

SpringBoot @Scheduled Cron表达式使用方式

《SpringBoot@ScheduledCron表达式使用方式》:本文主要介绍SpringBoot@ScheduledCron表达式使用方式,具有很好的参考价值,希望对大家有所帮助,如有... 目录Cron 表达式详解1. 表达式格式‌2. 特殊字符解析3. 常用示例‌4. 重点规则5. 动态与复杂场景‌

C++中::SHCreateDirectoryEx函数使用方法

《C++中::SHCreateDirectoryEx函数使用方法》::SHCreateDirectoryEx用于创建多级目录,类似于mkdir-p命令,本文主要介绍了C++中::SHCreateDir... 目录1. 函数原型与依赖项2. 基本使用示例示例 1:创建单层目录示例 2:创建多级目录3. 关键注

Spring Boot 集成 Quartz 使用Cron 表达式实现定时任务

《SpringBoot集成Quartz使用Cron表达式实现定时任务》本文介绍了如何在SpringBoot项目中集成Quartz并使用Cron表达式进行任务调度,通过添加Quartz依赖、创... 目录前言1. 添加 Quartz 依赖2. 创建 Quartz 任务3. 配置 Quartz 任务调度4. 启

C++中函数模板与类模板的简单使用及区别介绍

《C++中函数模板与类模板的简单使用及区别介绍》这篇文章介绍了C++中的模板机制,包括函数模板和类模板的概念、语法和实际应用,函数模板通过类型参数实现泛型操作,而类模板允许创建可处理多种数据类型的类,... 目录一、函数模板定义语法真实示例二、类模板三、关键区别四、注意事项 ‌在C++中,模板是实现泛型编程

kotlin的函数forEach示例详解

《kotlin的函数forEach示例详解》在Kotlin中,forEach是一个高阶函数,用于遍历集合中的每个元素并对其执行指定的操作,它的核心特点是简洁、函数式,适用于需要遍历集合且无需返回值的场... 目录一、基本用法1️⃣ 遍历集合2️⃣ 遍历数组3️⃣ 遍历 Map二、与 for 循环的区别三、高

Spring Boot中定时任务Cron表达式的终极指南最佳实践记录

《SpringBoot中定时任务Cron表达式的终极指南最佳实践记录》本文详细介绍了SpringBoot中定时任务的实现方法,特别是Cron表达式的使用技巧和高级用法,从基础语法到复杂场景,从快速启... 目录一、Cron表达式基础1.1 Cron表达式结构1.2 核心语法规则二、Spring Boot中定

C语言字符函数和字符串函数示例详解

《C语言字符函数和字符串函数示例详解》本文详细介绍了C语言中字符分类函数、字符转换函数及字符串操作函数的使用方法,并通过示例代码展示了如何实现这些功能,通过这些内容,读者可以深入理解并掌握C语言中的字... 目录一、字符分类函数二、字符转换函数三、strlen的使用和模拟实现3.1strlen函数3.2st

MySQL中COALESCE函数示例详解

《MySQL中COALESCE函数示例详解》COALESCE是一个功能强大且常用的SQL函数,主要用来处理NULL值和实现灵活的值选择策略,能够使查询逻辑更清晰、简洁,:本文主要介绍MySQL中C... 目录语法示例1. 替换 NULL 值2. 用于字段默认值3. 多列优先级4. 结合聚合函数注意事项总结C