kotlin 学习教程(五) |函数和函数式编程
1. 前言
我们知道,在程序中,通常情况下。一个类会有自己的方法(函数)以及属性,这些方法代表了该类的特性或者说具有的能力。今天我们一起来研究一下 kotlin中的函数。
2.如何声明一个函数
在 kotlin 中,我们通过关键字 fun 来声明一个函数
fun multiply(x:Int,y:Int):Int{
... //代码块
return x*y
}
如上面的代码所示:我们定义了函数multiply()并指定其返回类型为 Int 类型。
3.Lambda表达式
3.1 Lambda表达式介绍
从Java8 开始,Lambda表达式在 Lambda表达式,在其他的编程语言中(例如:Scala
语言),这种表达式是语法糖中的一种,在 kotlin 中,也支持这种语法,它允许把函数作为一个方法的参数,可以使代码变的更加简洁紧凑。
Lambda
表达式的本质其实是匿名函数
,因为在其底层实现中还是通过匿名函数
来实现的。但是我们在用的时候不必关心起底层实现。不过Lambda
的出现确实是减少了代码量的编写,同时也是代码变得更加简洁明了。
3.2 Java 8 Lambda 表达式
3.2.1 语法:
(parameters) -> expression
或
(parameters) ->{ statements; }
3.2.2 特征
-
可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
-
可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
-
可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
-
可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
3.2.3 代码如下:
// 1. 不需要参数,返回值为 5
() -> 5
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y
// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)
从上面可以看出,代码简洁了不少。
我们再来看一个例子,在java中假如要计算两个数的和,则代码如下:
import java.util.function.BiFunction;
public class helloword {
public static void main(String[] args) {
BiFunction<Integer,Integer,Integer> sum=(Integer x, Integer y) ->{
return x+y;
};
System.out.println("sum(3, 4) = " + sum.apply(3, 4));
}
}
运行结果如下:
Snipaste_2020-02-17_10-31-55.png
但是,在 java 8 以下版本,并不支持 Lambda 表达式,而这个时候.kotlin 完美的兼容了 java 8 以下版本对 lamdba 表达式的支持,并且能够进行混合开发。
3.3 kotlin Lamdba 表达式
好,我们再来看看 Android 中 kotlin 的使用,Kotlin语言中
3.3.1 Lamdab 表达式 的声明:
lambda表达式的完整语法如下:
{ params -> expressions }
-
params表示参数列表,expressions表示具体实现,可以是单行语句,也可以是多行语句。
-
Lamdba 表达式的值为大括号最后一行的值。
3.3,2 Lamdba 表达式的类型表示:
-
() ->unit 无参,返回值为Unit
-
(Int)-> Int 传入整型,返回一个整型
-
(String,(String)->String)->Boolean 传入字符串,Lamdba 表达式,返回 Boolean
3.3.3 Lamdba 表达式的调用:
- 使用 () 进行调用,相当于invoke()
3.3.4 Lamdba 表达式的简化:
args.forEach(){ println(it) } // 如果函数的最后一个参数是lamdba 表达式,则可以将lamdba 放在括号外面
args.forEach{ println(it) } // 如果函数参数只有一个lamdba表达式,则调用时小括号可以省略。
args.forEach(::print) //入参,返回值与形参一致的函数可以用函数引用的方式作为实参传入。
上述同样的功能,kotlin 语言的实现如下:
- 自定义函数来实现:
fun main(args: Array<String>) {
val result=sum(3,4)
println(result)
}
fun sum(arg1:Int,arg2:Int)=arg1+arg2
- 使用 Lamdba 表达式实现:
fun main(args: Array<String>) {
println(result(3, 4))
}
val result = { arg1: Int, arg2: Int ->
println("$arg1+$arg2=${arg1 + arg2}")
arg1 + arg2
}
我们再来看个例子,要求:遍历kotlin 中main函数的参数,
- 示例代码:Android 中最常见的点击事件
tv_toLogin.setOnClickListener(object:View.OnClickListener{
override fun onClick(v: View?) {
Toast.makeText(this,"onClick",Toast.LENGTH_SHORT).show()
}
})
- 使用 Lambda 表达式的点击事件
tv_toLogin.setOnClickListener({
Toast.makeText(this,"onClick",Toast.LENGTH_SHORT).show()
})
怎么样?有没有感觉 lamdba 表达式使用起来既简洁又优雅。
4. 高阶函数
4.1 什么是高阶函数
高阶函数就是以另一个函数作为参数或返回值的函数,Kotlin 可以以 lambda 或参数引用作为参数或返回值,所以,任何以 lambda 或函数引用作为参数或返回值的都是高阶函数。
4.2 常见的高阶函数
4.2.1 forEach()函数
fun main(args: Array<String>) {
val list= listOf(1,2,3,4,5,6,7,8)
val newList=ArrayList<Int>()
list.forEach{
val newElement=it *2+3
newList.add(newElement)
}
newList.forEach(::println)
}
运行结果如下:
Snipaste_2020-02-17_10-31-55.png4.2.2 :map()函数
map: 接受一个lambda表达式,并且有返回值,形成一个新的list,实现对集合中的元素进行修改
好,我们通过高阶函数 map 来实现如上的效果:
fun main(args: Array<String>) {
val list= listOf(1,2,3,4,5,6,7,8)
val newList=list.map {
it*2+3
}
println(newList)
}
运行结果如下:
Snipaste_2020-02-17_10-31-55.png以上代码中通过高阶函数 map 实现了对集合中每个元素乘以2再加3的操作,不用去遍历集合中每个元素,是不是简单了许多。
4.2.3 flatMap()函数
flatMap是map和flat两个函数的“复合逻辑,可以将集合中的数据进行合并成一个集合。
示例代码如下:
fun main(args: Array<String>) {
val list= listOf(
1..20,
21..30,
31..100
)
val flatList=list.flatMap {
it.map {
"NO.$it"
}
}
flatList.forEach{
println(it)
}
}
运行结果如下: 将集合中数字从1~100打印输出,每个数字前标有“NO.”
Snipaste_2020-02-17_10-31-55.png
4.2.4 filter()函数
传入Lambda 表达式为 true 是,保留该元素;使用filter对集合进行按条件过滤
fun main(args: Array<String>) {
val list= listOf(1,2,3,4,5,6,7,8,9)
val result=list.filter {
it%2==0
}
println(result)
}
运行结果如下:
Snipaste_2020-02-17_10-31-55.png
4.3 kotlin 中的 特殊函数
4.3.1 run()函数
该函数实际上可以说是let和with两个函数的结合体,run函数接收一个lambda函数为参数,以闭包形式返回,返回值为最后一行的值或者指定的return的表达式。
示例代码如下:
fun main(args: Array<String>) {
FunKotlin().myFun()
run {
FunKotlin().myFun()
}
run {
println("kotlin")
}
}
class FunKotlin{
fun myFun():String{
println("开始执行myFun()函数")
return "kotlin 中的特殊函数"
}
}
运行结果如下:
Snipaste_2020-02-17_10-31-55.png在上面的代码中,我们定义了myFun()函数并通过run() 进行调用,调用的结果即为myFun()的结果。
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()
}
如代码所示:我们传入block()参数,最终返回了block() 的执行结果。
4.3.2 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
}
如代码所示:我们传入 block() 函数,先是调用了block()函数,然后返回当前的调用者对象this,也就是说先执行完block()代码块逻辑后,再次返回当前的调用者对象。
示例代码如下:
fun main(args: Array<String>) {
testApply()
}
fun testApply(){
val list= mutableListOf<String>()
list.add("A")
list.add("B")
list.add("C")
list.add("D")
list.add("E")
println("普通写法:list=${list}")
val applyList=mutableListOf<String>().apply {
add("A")
add("B")
add("C")
add("D")
add("E")
println("使用apply 函数写法 this=${this}")
}
}
运行结果如下:
Snipaste_2020-02-17_10-31-55.png
如代码所示:我们的需求是创建一个集合并向其中添加元素"A",“B”,"C",“D”,“E”,然后打印出该集合,相比普通写法,使用apply() 函数显然简洁了许多。
4.3.3 let() 函数
let扩展函数的实际上是一个作用域函数,当你需要去定义一个变量在一个特定的作用域范围内,let函数的是一个不错的选择;let函数另一个作用就是可以避免写一些判断null的操作
源码如下:
/**
* 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)
}
如代码所示,我们重点看最后一行return block(this),就是说把当前调用对象作为参数传入block()代码块中。
示例代码如下:我们以Android中在适配器 adapter 中进行网络图片的加载。
context?.let {
Glide.with(it).load(item.envelopePic).crossFade().into(helper.getView<ImageView>(R.id.iv_envelopePic))
}
在上面的代码中,it 指代的即是 context,意为上下文。</pre>
4.3.4 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
}
看最后两行代码,先是执行了block(this),随后返回this,即当前的调用者对象。
示例代码如下:
fun testAlsoFun() {
val a = "ABC".also {
println(it) //输出:ABC
}
println(a) //输出:ABC
a.let {
println(it) //输出:ABC
}
}
fun main(args: Array<String>) {
testAlsoFun()
}
在上面的代码中,字符串“ABC”调用了also(),会打印出调用者 “ABC”.
4.3.5 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()函数传入了一个接收者对象receiver,然后使用该对 象receiver去调用传入的Lambda代码块receiver.block()。
- 示例代码在 Android中我们初始化一个控件并给其赋值,Java 语言实现如下:
TextView text=(TextView)findViewById(R.id.tv_text)
text.setText("哈哈哈")
text.setTextSize(23)
- kotlin 语言实现如下:
with(tv_text){
text="哈哈哈"
textSize=23
}
在上面的代码中,实现的功能是一样的,但是显然 kotlin 语言更加的简洁。