【Kotlin】高阶函数和Lambda

文章目录

    • 高阶函数
      • 抽象和高阶函数
        • 方法引用表达式使用场景
    • Lambda表达式
      • lambda表达式类型
      • Lambda开销
    • 闭包
      • Java实现闭包
      • Kotlin中的闭包

高阶函数

Kotlin天然支持了部分函数式特性。我们可以在一个函数内部定义一个局部函数

fun foo(x: Int) {fun double(y: Int): Int {return y * 2}println(double(x))
}

抽象和高阶函数

在我们以往熟悉的编程中,过程限制为只能接收数据为参数

由于我们经常会遇到一些同样的程序设计模式能够用于不同的过程,比如一个包含了正整数的列表,需要对它的元素进行各种转换操作,例如对所有元素都乘以3,或者都除以2。我们就需要提供一种模式,同时接收这个列表及不同的元素操作过程,最终返回一个新的列表

为了把这种类似的模式描述为相应的概念,我们就需要构造出一种更加高级的过程,表现为:接收一个或多个过程为参数,或者以一个过程作为返回结果。这个就是所谓的高阶函数,你可以把它理解为“以其他函数作为参数或返回值的函数”。高阶函数是一种更加高级的抽象机制,它极大地增强了语言的表达能力。

方法引用表达式使用场景

此外,我们还可以直接通过这种语法,来定义一个类的构造方法引用变量

class Book(val name: String) {fun main() {val getBook = ::Bookprintln(getBook("Test").name)}
}

可以发现,getBook类型为(name: String) -> Book。类似的道理,如果我们要引用某个类的成员变量,如Book类中的name,就可以这样引用:

Book::name

以上创建的Book::name的类型为(Book) -> String。当我们再对Book类对象的集合应用一些函数式API的时候,这会显得格外有用,比如:

fun main(args: Array<String>) {val bookNames = listOf (Book("Thinking in java")Book("Dive into Kotlin")    ).map(Book::name)println(bookNames)
}

首先来看一下Lambda的定义,如果用最直白的语言来阐述的话,Lambda就是一小段可以作为参数传递的代码。从定义上看,这个功能就很厉害了,因为正常情况下,我们向某个函数传参时只能传入变量,而借助Lambda却允许传入一小段代码。这里两次使用了“一小段代码”这种描述,那么到底多少代码才算一小段代码呢?Kotlin对此并没有进行限制,但是通常不建议在Lambda表达式中编写太长的代码,否则可能会影响代码的可读性。

Lambda的语法:

  • 一个Lambda表达式必须通过{}来包裹
  • 如果Lambda声明了参数部分的类型,且返回值类型支持类型推导,那么Lambda变量就可以省略函数类型声明
  • 如果Lambda变量声明了函数类型,那么Lambda的参数部分的类型就可以省略

此外,如果Lambda表达式返回的不是Unit,那么默认最后一行表达式的值类型就是返回值类型,如:

val foo = { x: Int -> val y = x + 1y // 返回值是y           
}

Lambda表达式

{参数名1: 参数类型, 参数名2: 参数类型 -> 函数体}

“Lambda 表达式”(lambda expression)其实就是匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。Lambda表达式可以表示闭包。

Java 8的一个大亮点是引入Lambda表达式,使用它设计的代码会更加简洁。

// 没有使用Lambda的老方法:   
button.addActionListener(new ActionListener(){public void actionPerformed(ActionEvent ae){System.out.println("Actiondetected");}
});
// 使用Lambda:  
button.addActionListener(()->{ System.out.println("Actiondetected");
});// 不采用Lambda的老方法: 
Runnable runnable1=new Runnable(){@Overridepublic void run(){System.out.println("RunningwithoutLambda");}
};
// 使用Lambda:   
Runnable runnable2=()->{System.out.println("RunningfromLambda");
};

Lambda能让代码更简洁,Kotlin的支持如下:

  • lambda表达式总是被大括号括着
  • 其参数(如果有的话)在->之前声明(参数类型可以省略),
  • 函数体(如果存在的话)在->后面。

Lambda表达式是定义匿名函数的简单方法。由于Lambda表达式避免在抽象类或接口中编写明确的函数声明,进而也避免了类的实现部分,
所以它是非常有用的。

先看一个例子:

fun compare(a: String, b: String): Boolean {return a.length < b.length
}
max(strings, compare)

就是找出strings里面最长的那个。但是我个人觉得compare还是很碍眼的,因为我并不想在后面引用他,那我怎么办呢,就是用“匿名函数”方式。

max(strings, (a,b)->{a.length < b.length})

(a,b)->{a.length < b.length}就是一个没有名字的函数,直接作为参数赋给max方法的第二个参数。但这个方法有很多东西都没有写明,如:

  • 参数的类型
  • 返回值的类型

但这些真的必要吗?a.length < b.length很明显返回一个Boolean的值,再就是max的定义中肯定也定义了这个函数的参数类型和返回值类型。
这么明显的事为什么不让计算机自己去做而要让人写代码去做呢?这就是匿名函数的好处了。到这里,我们已经和Lambda很接近了。

val sum: (Int, Int) -> Int = { x, y -> x + y }

Lambda表达式就是被大括号括着的那一部分,在->符号之前有参数声明,函数体跟在一个->符号之后。
而且此Lambda表达式之前有一个匿名的函数声明(在此例中两个Int型的输入,一个Int型的返回值),这个声明是可以不使用的。
则此Lambda表达式变成val sum = { x: Int, y: Int -> x + y },此时Lambda表达式会根据主体中的最后一个(或可能是单个)表达式会视为
返回值。当然,在某些特定情况下,xy的类型了是可以推断的,所以val sum = { x, y -> x + y }

通过调用lambda来执行它的代码你可以使用invoke函数调用lambda,并传入参数的值。例如,以下代码定义了变量addInts,并将用于将两个Int参数相加的lambda赋值给它。然后代码调用了该lambda,传入参数值6和7,并将结果赋值给变量result:

val addInts = { x: Int, y: Int -> x + y }
val result = addInts.invoke(6, 7)
// 还可以使用如下快捷方式调用lambda:
val result = addInts(6, 7)

lambda表达式类型

就像任何其他类型的对象一样,lambda也具有类型。然而,lambda类型的不同点在于,它不会为lambda的实现指定类名,而是指定lambda的参数和返回值的类型。lambda类型的格式如下:

(parameters) -> return_type

因此,如果你的lambda具有单独的Int参数并返回一个字符串,如下代码所示:

val msg = { x: Int -> "xxx" }

其类型为:

(Int) -> String

如果将lambda赋值给一个变量,编译器会根据该lambda来推测变量的类型,如上例所示。然而,就像任何其他类型的对象一样,你可以显式地定义该变量的类型。例如,以下代码定义了一个变量add,该变量可以保存对具有两个Int参数并返回Int类型的lambda的引用:

val add: (Int, Int) -> Intadd = { x: Int, y: Int -> x + y }

Lambda类型也被认为是函数类型。

当Lambda表达式的参数列表中只有一个参数时,也不必声明参数名,而是可以使用it关键字来代替.

val list = listOf("Apple", "Bnana", "Orange", "Pear")
val maxLengthFruit = list.maxBy {it.length}//按照长度排序

Lambda开销

fun foo(int: Int) = {print(int)
}
listOf(1, 2, 3).forEach { foo(it) } // 对一个整数列表的元素遍历调用foo,我的评价是:高级

这里,你可定会纳闷it是啥?其实它也是Kotlin简化Lambda表达的一种语法糖,叫做单个参数的隐式名称,代表了这个Lambda所接收的单个参数。这里的调用等价于:

listOf(1, 2, 3).forEach { item -> foo(item) }

如果lambda具有一个单独的参数,而且编译器能够推断其类型,你可以省略该参数,并在lambda的主体中使用关键字it指代它。要了解它是如何工作的,如前所述,假设使用以下代码将lambda赋值给变量:

val addFive: (Int) -> Int = { x -> x + 5 }

由于lambda具有单独的参数x,而且编译器能够推断出x为Int类型,因此我们可以省略该x参数,并在lambda的主体中使用it替换它:

val addFive: (Int) -> Int = { it + 5 }

在上述代码中,{it+5}等价于{x->x+5},但更加简洁。请注意,你只能在编译器能够推断该参数类型的情况下使用it语法。例如,以下代码将无法编译,因为编译器不知道it应该是什么类型:

val addFive = { it + 5 } // 该代码无法编译,因为编译器不能推断其类型

我们看一下foo函数用IDE转换后的Java代码:

@JvmStatic
@NotNull
public static final Function0 foo(final int var0) {return (Function0)(new Function0() {// $FF: synthetic method// $FF: bridge methodpublic Ojbect invoke() {this.invoke();return Unit.INSTANCE;}public final void invoke() {int var1 = var0;System.out.printlln(var1);}});
}

以上是字节码反编译的Java代码,从中我们可以发现Kotlin实现Lambda表达式的机理。

闭包

在Kotlin中,你会发现匿名函数体、Lambda在语法上都存在“{}",由这对花括号包裹的代码如果访问了外部环境变量则被称为一个闭包。一个闭包可以被当做参数传递或直接使用,它可以简单的看成”访问外部环境变量的函数“。Lambda是Kotlin中最常见的闭包形式。

与Java不一样的地方在于,Kotlin中的闭包不仅可以访问外部变量,还能够对其进行修改(我有点疑惑,Java为啥不能修改?下面说),如下:

var sum = 0
listOf(1, 2, 3).filter { it > 0 }.forEach {sum += it
}
println(sum)  // 6

看到这里我是懵逼的? 到底什么是闭包? 闭包有什么作用?

闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。–百度百科

第一句总结的很简洁了:闭包就是能够读取其他函数内部变量的函数。

Java实现闭包

在Java8之前,是不支持闭包的,但是可以通过“接口+匿名内部类”来实现一个伪闭包的功能,为什么说是伪闭包?

简单的说就是: JVM在内部类初始化的时候帮我们拷贝了一个局部变量的备份到内部类中,并且把它的值复制到了堆内存中(变量有两份,同样的名字,一个在局部变量中用,一个在内部类中)。所以要是不用final修饰,那你后面把外部类中的变量的值修改了,而内部类中拷贝的值还是原来的,那这样岂不是两边的值不一样了? 所以不能让你改,必须加final。

Kotlin中的闭包

想要理解kotlin中闭包的实现,首先要懂kotlin中的一个概念:在Kotlin中,函数是“一等公民”。

对比一下java和kotlin更好理解:

在java中是不支持这种写法的,因为函数是“二等公民”。

下面再看下kotlin代码:

fun test(): () -> Unit {var a = 0return fun() {a++println(a)}
}fun main() {val t = test()t()
}

内部函数很轻松地调用了外部变量a。这只是一个最简单的闭包实现。按照这种思想,其他的实现例如:函数、条件语句、Lambda表达式等等都可以理解为闭包,这里不再赘述。不过万变不离其宗,只要记得一句话:闭包就是能够读取其他函数内部变量的函数。就是一个函数A可以访问另一个函数B的局部变量,即便另一个函数B执行完成了也没关系。目前把满足这样条件的函数A叫做闭包。

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

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

相关文章

flex布局实战之自动填充剩余

案例目标 文字部分自适应并且居中 图中是一个弹窗&#xff0c;我现在使用flex的布局来实现&#xff0c;标题和关闭按钮。因为是uni-app,所以标签是view 。你可以自行替换为 代码 <view class"popup-box"><view class"title"><view class&…

线程的状态以及状态转移

一. 线程的状态 NEW: 线程刚被创建, 但是并未启动. 还没调用start方法.RUNNABLE: 这里没有区分就绪和运行状态. 因为对于Java对象来说, 只能标记为可运行, 至于什么时候运行, 不是JVM来控制的了, 是OS来进行调度的, 而且时间非常短暂, 因此对于Java对象的状态来说, 无法区分.T…

机器学习探索计划——KNN实现Iris鸢尾花分类

文章目录 1. 加载数据集2.拆分数据集3.预测4.评价 1. 加载数据集 import numpy as np from sklearn import datasetsiris datasets.load_iris()iris.keys()dict_keys([data, target, frame, target_names, DESCR, feature_names, filename, data_module])X iris.data X.shap…

97、Text2NeRF: Text-Driven 3D Scene Generation with Neural Radiance Fields

简介 论文地址 使用扩散模型来推断文本相关图像作为内容先验&#xff0c;并使用单目深度估计方法来提供几何先验&#xff0c;并引入了一种渐进的场景绘制和更新策略&#xff0c;保证不同视图之间纹理和几何的一致性 实现流程 简单而言&#xff1a; 文本-图片扩散模型生成一…

STM32入门学习(一):STM32 简介与软件安装

参考引用 STM32 入门教程-江科协 1. STM32 简介 1.1 STM32 套件介绍 1.2 STM32 简介 STM32 是 ST 公司基于 ARM Cortex-M 内核开发的 32 位微控制器 应用&#xff1a;嵌入式领域&#xff0c;如智能车、无人机、机器人、无线通信、物联网、工业控制、娱乐电子产品等 1.3 ARM …

go对rabbitmq基本操作

一、安装rabbitmq 1、直接使用docker拉取镜像 docker pull rabbitmq:3.82、启动容器 docker run \-e RABBITMQ_DEFAULT_USERadmin \-e RABBITMQ_DEFAULT_PASS123456 \-v mq-plugins:/plugins \--name rabbit01 \--hostname rabbit01 --restartalways \-p 15672:15672 \-p 5672:…

《C++PrimePlus》第9章 内存模型和名称空间

9.1 单独编译 Visual Studio中新建头文件和源代码 通过解决方案资源管理器&#xff0c;如图所示&#xff1a; 分成三部分的程序&#xff08;直角坐标转换为极坐标&#xff09; 头文件coordin.h #ifndef __COORDIN_H__ // 如果没有被定义过 #define __COORDIN_H__struct pola…

【开源】基于Vue.js的城市桥梁道路管理系统的设计和实现

项目编号&#xff1a; S 025 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S025&#xff0c;文末获取源码。} 项目编号&#xff1a;S025&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统展示四、核心代码4.1 查询城市桥…

学生信息管理系统程序Python

系统主界面 在该界面中可以选择要使用功能对应的菜单进行不同的操作。在选择功能菜单时&#xff0c;有两种方法&#xff0c; 一种是输入1&#xff0c;另一种是按下键盘上的↑或↓方向键进行选择。这两种方法的结果是一样的&#xff0c;所以使用哪种方法都可以。 &#xff08;…

时间序列预测 — Informer实现多变量负荷预测(PyTorch)

目录 1 实验数据集 2 如何运行自己的数据集 3 报错分析 1 实验数据集 实验数据集采用数据集4&#xff1a;2016年电工数学建模竞赛负荷预测数据集&#xff08;下载链接&#xff09;&#xff0c;数据集包含日期、最高温度℃ 、最低温度℃、平均温度℃ 、相对湿度(平均) 、降雨…

什么是零拷贝 、零拷贝优化方案 - 真正的零拷贝,哪些地方会用到零拷贝技术

文章目录 什么是零拷贝3、零拷贝优化方案 - 真正的零拷贝哪些地方会用到零拷贝技术 现在来谈谈零拷贝&#xff0c;以及在开发中哪些地方使用到零拷贝。 开干… 什么是零拷贝 零拷贝指的是&#xff0c;从一个存储区域到另一个存储区域的copy任务无需CPU参与就可完成。零拷贝的底…

setInterval 和 setTimeOut 区别

在 JavaScript 中&#xff0c;setInterval 和 setTimeout 都是用于执行函数或代码片段的定时器函数。它们之间的区别在于触发执行的时间和执行的次数。 setInterval&#xff1a; setInterval 函数会按照指定的时间间隔重复执行函数或代码片段。语法&#xff1a;setInterval(ca…

徕芬不是满分:自称超越戴森,用户称多次故障,品控仍是老大难?

撰稿|行星 来源|贝多财经 “双十一”购物节落下帷幕后&#xff0c;各大品牌纷纷公布“战报”。其中&#xff0c;高速吹风机品牌徕芬&#xff08;也称“徕芬科技”&#xff09;销售额超4.4亿元&#xff0c;全系产品销量超过80万台&#xff0c;高速吹风机系列单品(LF03、SE)销售…

来自Microsoft Teams的摄像头背景图片

原文件在&#x1f446;&#xff0c;下面是预览图 如果你安装了Microsoft Teams也可以搜索MSTeams&#xff0c;就在MSTeams/Backgrounds

【anaconda】numpy.dot 向量点乘小技巧

假设向量A[1,1], 向量B[2,3]。如果想知道他们的内积就可以输入如下代码: 当然&#xff0c;如果是两个列向量相乘&#xff0c;肯定是不对的 但是如果没有维度也一样可以求得内积&#xff0c;而且结果不会套在列表里

AI和人工智能与机器学习全景报告

今天分享的是AI系列深度研究报告&#xff1a;《AI和人工智能与机器学习全景报告》。 &#xff08;报告出品方&#xff1a;appen&#xff09; 报告共计&#xff1a;30页 获取 数据获取仍是AI应用构建团队的主要瓶颈。 原因各不相同。例如&#xff0c;特定用例的数据可能不足…

Day02嵌入式---按键控灯

一、简单介绍 按键控制灯开关是一种常见的嵌入式系统示例项目&#xff0c;它通常用于演示嵌入式系统的基本控制能力。该项目由一个或多个LED和一个按键组成。通过按下按键&#xff0c;可以控制LED的开关状态&#xff0c;从而实现灯的亮灭控制。 二、查看功能手册 2.1 查看硬件…

基于单片机压力传感器MPX4115检测-报警系统proteus仿真+源程序

一、系统方案 1、本设计采用这51单片机作为主控器。 2、MPX4115采集压力值、DS18B20采集温度值送到液晶1602显示。 3、按键设置报警值。 4、蜂鸣器报警。 二、硬件设计 原理图如下&#xff1a; 三、单片机软件设计 1、首先是系统初始化 /*********************************…

鸿蒙开发之android开发人员指南《基础知识》

基于华为鸿蒙未来可能不再兼容android应用&#xff0c;推出鸿蒙开发系列文档&#xff0c;帮助android开发人员快速上手鸿蒙应用开发。 1. 鸿蒙使用什么基础语言开发&#xff1f; ArkTS是鸿蒙生态的应用开发语言。它在保持TypeScript&#xff08;简称TS&#xff09;基本语法风…

【免费使用】基于PaddleSeg开源项目开发的人像抠图Web API接口

基于PaddleSeg开源项目开发的人像抠图API接口&#xff0c;服务器不存储照片大家可放心使用。 1、请求接口 请求地址&#xff1a;http://apiseg.hysys.cn/predict_img 请求方式&#xff1a;POST 请求参数&#xff1a;{"image":"/9j/4AAQ..."} 参数是jso…