kotlin高阶函数

kotlin高阶函数

函数式API:一个函数的入参数为Lambda表达式的函数就是函数式api
例子:

public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {return filterTo(ArrayList<T>(), predicate)
}

上面这段函数: 首先这个函数是一个泛型函数
泛型函数的定义:就是我们在写完一个函数后,只知道一个总的类型,而这个总的类型下有很多继承了这个总类型的类,在返回时我们不知道这个函数具体返回哪个子类,这时我们就可以在这个函数前面加一个泛型,泛型中放这些子类的基类,上面的filter方法也可以看作 Iterable的扩展方法

例子:

// A为基类
public class A{}
public class  B extends A{}public class  C extends A{}	

// 我们不知道下面这个方法具体返回A,B,C到底哪一个类型时,因为类B和C继承自A,这时我们就可以将这个返回类型指定为基类型A

public static <A> test(){}

下面我写kotlin的写法

class ReportV2Controller : Controller(){
}

和上面java一样有多个类继承自Controller()这个类,kotlin中继承都是以函数的形式,因为这样写直观的表现了自类具体会调用基类的哪个构造函数

	public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {return filterTo(ArrayList<T>(), predicate)
}		

上面的filter方法是迭代集合Iterable,但是在kotlin中这个Iterable是一个被inline修饰的内联函数

内联函数的出现的原因:
1.首先在kotlin中有高阶函数的原因,因为高阶函数的传参可以是一个函数,而且出参也可以为一个函数

// 下面这段代码原作者为csdn: Mr YiRan
fun num1AndNum2(num1:Int,num2:Int,operator:(Int,Int)->Int):Int{val result=operator(num1,num2)return result
}

上面这段代码高阶函数num1AndNum2入参为两个Int类型与一个函数类型operator,这个operator又接收两个int类型的参数,这个operator返回类型为int,operator返回的类型int同时也是num1AndNum2高阶函数的返回类型,同时num1AndNum2函数的入参数,也可以当作函数operator的入参

fun num1AndNum2(num1:Int,num2:Int,operator:(Int,Int)->Int):Int{val result=operator(num1,num2)return result
}// 定义两个具体的运算函数
fun plus(num1: Int,num2: Int):Int{
return num1+num2   
}
fun minus(num1: Int,num2: Int):Int{return num1-num2
}fun main(){val num1=100val num2=80// 这是一个函数引用方式的写法,表示将plus()和minus()函数作为参数传递给num1AndNum2()函数val result1= num1AndNum2(num1,num2,::plus)val result2 = num1AndNum2(num1, num2, ::minus)println("result1 is $result1")println("result2 is $result2")
}fun main(){
val num1=100
val num2=80
// 上面的函数引用写法的调用可以改为Lambda表达式的方式来调用高阶函数
val result1=num1AndNum2(num1,num2){ n1,n2 ->
n1+n2
}
val result2=num1AndNum2(num1,num2){ n1,n2 ->
n1-n2
}
println("result1 is $result1")
println("result2 is $result2")
}

Lambda表达式的优点与缺点

优点 :
如果一个抽象类或者接口中只有一个方法,我们又不想实例化这个类的对象就想调用这个方法,而且也不想将这个类中的方法标记为静态的方法,就可以用匿名内部类的方式类写,但是这样写代码很不美观,所以就简化成Lambda表达式的方式来写

缺点:
Lambda表达式的方式,系统会默认实例化一个匿名内部类的方式,就会造成额外的内存开销与cpu性能损耗
因为就算高阶函数中的传参写成Lambda表达式的方式,其内部运行顺序也为先实例化最外层类的对象,然后通过对象去引用当前这个对象的方法,将当前对象的方法存入方法区进行压栈然后再实例化一个Lambda表达式的匿名内部类,再通过这个对象去引用这个匿名内部类中的方法,再进行压榨

kotlin是如何解决这种因为传入Lambda表达式写法的性能开销的

内联函数

在定义高阶函数时加上inline关键字,就表示此高阶函数是一个内联函数

inline fun num1AndNum2(num1:Int,num2:Int,operation:(Int,Int)->Int):Int{
val result=operation(num1,num2)
return result
}

如何消除 Lambda表达式额外开销
简单来说就是:在调用传参为 Lambda表达式的高阶函数时,将 Lambda表达式的函数体直接拷贝在调用参数为 Lambda表达式的高阶函数后方
示例
在这里插入图片描述
在这里插入图片描述
1.首先将 Lambda表达式的入参拷贝到函数类型参数的参数入参列表中
2.其次再将内联函数的方法体拷贝到调用该高阶函数的地方
3.总结为:将实例化匿名内部类调用方法压栈改为方法入参数拷贝和方法体拷贝的过程

下面给一个返回值是一个函数的高阶函数
示例

/**

  • 定义一个返回值是函数的高阶函数
  • @param name 入场
  • @return 返回一个函数或者lambda
    */
    fun highFunction2(name:String):(Int) -> Int{
    if (name == “A”){
    // 这里返回一个函数引用,意思调用returnFun函数
    // 上面的highFunction2函数入参为两个一个String类型的name,还有一个入参是一个参数为一个int类型的Lambda表达式
    // 如果想调用returnFun函数那highFunction2函数传入的Lambda表达式入参类,数量与顺序必须完全与函数returnFun一致
    return ::returnFun
    }
    //返回lambda
    return {a -> a + 10}
    }

/**

  • 作为高阶函数的返回函数
    */
    fun returnFun(a:Int):Int{
    return a * 100
    }
//使用高阶函数
val res = highFunction2("A")
println(res(20)) //打印2000
val res2 = highFunction2("B")
println(res2(20)) //打印30

扩展函数

fun main() {open class Shapeclass Rectangle: Shape()fun Shape.getName() = "Shape"fun Rectangle.getName() = "Rectangle"fun printClassName(s: Shape) {println(s.getName())}    printClassName(Rectangle())

上面函数的最终返回结果是打印"Shape",因为虽然printClassName的函数的传入参数是Rectangle()函数,但是最终会调用Shape类的getName()函数,这是因为最终返回的类型取决于printClassName函数传入参数s的类型

成员函数与扩展函数

如果一个类定义有一个成员函数与一个扩展函数,而这两个函数又有相同的接收者类型、 相同的名字,并且都适用给定的参数,这种情况总是取成员函数
示例

fun main() {class Example {fun printFunctionType() { println("Class method") }}fun Example.printFunctionType() { println("Extension function") }Example().printFunctionType()
}

这里最后会输出:“Class method”,因为是以类的成员函数为准的,优先级为先成员函数
如果是扩展类型的重载,那么以传入参数为准
由于静态调用扩展方法是在编译时执行,因此,如果父类和子类都扩展了同名的一个扩展方法,引用类型均为父类的情况下,会调用父类的扩展方法
示例

fun main() {class Example {fun printFunctionType() { println("Class method") }}fun Example.printFunctionType(i: Int) { println("Extension function") }Example().printFunctionType(1)
}

最后输出结果为:Extension function

top-level函数

不依赖于任何类的静态函数,经Kotlin编译成Java文件后,成为:<静态函数所在的文件名> + "Kt"的Java类的静态成员函数。如果原kotlin文件的文件名首字母为小写时,转换成大写。
示例

/** joinsample.kt */
package com.example.kotlinimport java.lang.StringBuilderfun <T> joinToString(collection: Collection<T>,separator: String = ", ",prefix: String = "",postfix: String = ""
): String {val result = StringBuilder(prefix)for ((index, element) in collection.withIndex()) {if (index > 0) {result.append(separator)}result.append(element)}result.append(postfix)return result.toString()
}
import com.example.kotlin.JoinsampleKt;
import java.util.Arrays;
import java.util.List;public class JavaSample {public static void main(String[] args) {List<String> list = Arrays.asList("hello", "world");JoinsampleKt.joinToString(list, ", ", "", "");}
}

扩展属性

由于扩展没有实际的将成员插入类中,因此对扩展属性来说幕后字段是无效的。这就是为什么扩展属性不能有初始化器。他们的行为只能由显式提供的 getters/setters 定义。

扩展声明为成员

就是说有两个类,可以在其中一个类中为另一个类声明一个扩展函数,被声明的扩展函数,此时是当前类的成员函数

示例

class Host(val hostname: String) {fun printHostname() { print(hostname) }
}class Connection(val host: Host, val port: Int) {fun printPort() { print(port) }// 这里可以为Host类定义扩展函数,是因为类Connection的主构造函数中将Host类传入进来fun Host.printConnectionString() {printHostname()   // 调用 Host.printHostname()print(":")printPort()   // 调用 Connection.printPort()}fun connect() {/*……*/host.printConnectionString()   // 调用扩展函数}
}fun main() {// 这里直接使用类Connection的主构造函数传入Host类然后调用Host的主构造函数,然后调用Host类的printHostname()函数// 被定义的扩展函数必须在定义该扩展函数的类中使用// 意思就是说Host的printConnectionString()函数必须在类Connection中使用//  Connection(Host("kotl.in"),443).printConnectionString()Connection(Host("kotl.in"), 443).connect()//Host("kotl.in").printConnectionString(443)  // 错误,该扩展函数在 Connection 外不可用
}
class Connection {// 这里给Host类定义一个扩展函数// 在这个扩展函数中调用toString()// 因为在kotlin中所有的类都继承自Any类,而toString方法是属于Any类的// 如果想调用Connection类的toString()方法需要向下面那种写法// this@Connection.toString()fun Host.getConnectionString() {toString()         // 调用 Host.toString()this@Connection.toString()  // 调用 Connection.toString()}
}

泛型中的in和out

这两个泛型的定义为逆变与携变
示例

// 逆变
interface Consumer<in T> {fun consume(item: T)
}

简单来说就是在接口中in标记的泛型类型T,要作为函数consume的入参类型

// 携变
interface Production<out T> {fun produce(): T
}

简单来说就是接口泛型中out标记的T要做为函数produce返回的类型

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/9145.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SciencePub学术 | 物联网类重点SCIEEI征稿中

SciencePub学术 刊源推荐: 物联网类重点SCIE&EI征稿中&#xff01;信息如下&#xff0c;录满为止&#xff1a; 一、期刊概况&#xff1a; 物联网类重点SCIE&EI 【期刊简介】IF&#xff1a;7.5-8.0&#xff0c;JCR1区&#xff0c;中科院1/2区TOP&#xff1b; 【出版社…

速度优化:重新认识速度优化

作者&#xff1a;helson赵子健 应用的速度优化是我们使用最频繁&#xff0c;也是应用最重要的优化之一&#xff0c;它包括启动速度优化&#xff0c;页面打开速度优化&#xff0c;功能或业务执行速度优化等等&#xff0c;能够直接提升应用的用户体验。因此&#xff0c;只要是 An…

uniapp 中 的progress加载进度条 的使用,在 页面显示数据加载的进度条,使用户的使用体验效果更好

学习目标&#xff1a; 学习目标如下&#xff1a; 例如&#xff1a; uniapp 中 的progress加载进度条 的使用&#xff0c;在 页面显示数据加载的进度条&#xff0c;使用户的使用体验效果更好 学习内容&#xff1a; 学习内容如下所示&#xff1a; 相关属性的说明 进度条的显…

[ELK安装篇]:基于Docker虚拟容器化(主要LogStash)

文章目录 一&#xff1a;前置准备-(参考之前博客)&#xff1a;1.1&#xff1a;准备Elasticsearch和Kibana环境&#xff1a;1.1.1&#xff1a;地址&#xff1a;https://blog.csdn.net/Abraxs/article/details/128517777 二&#xff1a;Docker安装LogStash(数据收集引擎&#xff…

【C#】医学实验室云LIS检验信息系统源码 采用B/S架构

基于B/S架构的医学实验室云LIS检验信息系统&#xff0c;整个系统的运行基于WEB层面&#xff0c;只需要在对应的工作台安装一个浏览器软件有外网即可访问&#xff0c;技术架构&#xff1a;Asp.NET CORE 3.1 MVC SQLserver Redis等。 一、系统概况 本系统是将各种生化、免疫、…

【MySQL】内置函数

目录 一、日期函数 1、获得年月日 2、获得时分秒 3、获得时间戳 4、在日期的基础上加日期 5、在日期的基础上减去时间 6、计算两个日期之间相差多少天 7、案例 二、字符串函数 1、获取emp表的ename列的字符集 2、要求显示exam_result表中的信息 3、求学生表中学生姓…

【Git】git仓库完整迁移

代码仓库&#xff0c;在公司有两个团队在做&#xff0c;并且gitlab所在环境不互通。有一个团队做的时间久一点&#xff0c;另一个团队想要用并做一些定制。就需要将代码转移到另一个gitlab管理。 参考&#xff1a;【Git】git仓库完整迁移&#xff08;代码&#xff0c;分支&…

全志F1C200S嵌入式驱动开发(lcd屏幕驱动)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 lcd RGB屏幕作为比较经济、实用的显示工具,在实际场景中使用较多。它的信号来说,一般也比较简单,除了常规的数据信号,剩下来就是行同步、场同步、数据使能和时钟信号了。数据信…

el-table 表格头部合并

<el-table v-loading"listLoading" :key"tableKey" :data"list" stripe border fit highlight-current-rowstyle"width: 100%;" size"mini"><el-table-column label"第一行" align"center">…

免费的音频转换器带你突破音频格式束缚

曾经有一个名叫吴欢的音乐爱好者&#xff0c;他热爱收集各种音频文件&#xff0c;从经典的老歌到新潮的流行曲&#xff0c;样样都不放过。然而&#xff0c;他遇到了一个令人头疼的问题&#xff1a;因为音频格式的不同&#xff0c;他无法将一些珍贵的音乐记录转化为文字形式。他…

面试题 汇总

一、 七层模型和五层模型以及对应的作用 二、 TCP和UDP的区别 UDPTCP是否连接无连接面向连接是否可靠不可靠传输,不使用流量控制和拥塞控制可靠传输,使用流量控制和拥塞控制连接对象个数支持一对一,一对多,多对一和多对多交互通信只能是一对一通信传输方式面向报文面向字节…

01 Excel常用高频快捷键汇总

目录 一、简介二、快捷键介绍2.1 常用基本快捷键1 复制&#xff1a;CtrlC2 粘贴&#xff1a;CtrlV3 剪切&#xff1a;CtrlX4 撤销&#xff1a;CtrlZ5 全选&#xff1a;CtrlA 2.2 常用高级快捷键1 单元格内强制换行&#xff1a;AltEnter2 批量输入相同的内容&#xff1a;CtrlEnt…

vue3 - element-plus 上传各种 word pdf 文件、图片视频并上传到服务器功能效果,示例代码开箱即用。

效果图 在 vue3 项目中,使用 element plus 组件库的 el-upload 上传组件,进行文件、图片图像的上传功能示例。 完整代码 可直接复制,再改个接口地址。 在这里上传图片和文件是分成

嵌入式和 Java 走哪条路?

JAVA和嵌入式各有千秋&#xff0c;看个人取舍。 想挣钱挣得快一点&#xff0c;挣得多一点&#xff0c;那就选Java&#xff0c;但有中年危机。 想细水长流一点的&#xff0c;选嵌入式&#xff0c;挣钱挣得慢一点&#xff0c;也稳一点&#xff0c;挣得久一点&#xff0c;中年危…

IO进、线程——标准文件IO和时间函数

1.文件IO 最直观的系统调用 1.1打开文件 int open(const char *pathname, int flags, mode_t mode);功能&#xff1a;打开/创建后打开一个文件 返回值&#xff1a;成功返回文件描述符&#xff0c;失败-10 —— 标准输入 1 —— 标准输出 2 —— 标准出错参数说明&#xf…

linux操作历史history定制

history记录 Linux中历史操作记录history是一个很有用的功能&#xff0c;有时忘记了&#xff0c;翻翻以前的命令&#xff0c;十分方便。 # 展示所有历史记录 history # 筛选历史记录 history | grep nginx # 清除全部记录 -c history -c # 指定删除某一行,15是行号 history -…

ACwing 1081. 度的数量

文章目录 题意思路代码 题意 给你一段区间[x, y]求其中满足一个数恰好等于K个互不相等的B的整数次幂之和的数的个数。 例如&#xff1a;x 15, y 20, k 2, b 2&#xff0c;那么对于这个区间有且仅有三个数满足题意&#xff1a; 17 2 4 2 0 10001 17 2^42^0 10001 1724…

linux | vscode | makefile | c++编译和调试

简单介绍环境&#xff1a; vscode 、centos、 gcc、g、makefile 简单来说就是&#xff0c;写好项目然后再自己写makefile脚本实现编译。所以看这篇博客的用户需要了解gcc编译的一些常用命令以及makefile语法。在网上看了很多教程&#xff0c;以及官网也看了很多次&#xff0c;最…

前端开发实习总结参考范文

▼前端开发实习总结篇四 读了三年的大学&#xff0c;然而大多数人对本专业的认识还是不那么透彻&#xff0c;学的东西真正能够学以致用的东西很少&#xff0c;大家都抱怨没有实践的机会&#xff0c;在很多同学心里面对于本专业还是很茫然。直到即将毕业的时候才知道我们以前学…

uni-app中的uni.requireNativePlugin()

这个方法是用来引入原生插件的方法&#xff0c;自 HBuilderX 1.4 版本起&#xff0c;uni-app 支持引入原生插件&#xff0c;使用方式如下&#xff1a; const PluginName uni.requireNativePlugin(PluginName); // PluginName 为原生插件名称 引入插件的类型有三种&#xff1…