Kotlin高阶函数入门
1.1 如何理解Java中的lambda表达式
在讲解Kotlin的高阶函数之前,我们先来了解一下它的前身:Java中的Lambda表达式
Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中),使用 Lambda 表达式可以使代码变的更加简洁紧凑。
//Lambda的形式如下:
AInterface a = (param1 , param2) -> {//do some operations}
我们知道对于一个已声明的java对象,我们可以为其赋值;这个“值”,或是基本类型或是一个该对象在堆中的地址。但是上述代码看起来更像是给对象赋了一段“代码块”。那么上述代码块代表的是什么呢?
这就要介绍一下Lambda应用的原理了:
在Java8中不仅推出了Lambda表达式,还拓展了接口的用法,现在接口中可以包括默认方法(该特性的详细信息不做展开),静态方法,和抽象方法。
只有当接口中只包括一个抽象方法时,才能使用Lambda的方式对该接口进行声明。如果我将上述代码写成下面这种形式,你肯定可以很清楚明白代码的含义:
interface AInterface {
void test (int param1 , int param2);
}
AInterface a = new AInterface {
@Override
void test (int prarm1 , int param2){
//do some operations
}
}
实际上Lambda表达式就是对上述代码进行了简化,因为该接口中只包括一个抽象方法,所以编译器会将Lambda表达式的内容自动识别成被重写的方法。这同时也说明了为什么在Lambda表达式中想要使用外部的变量必须是final类型的。(同匿名内部类的原理)
到这里可能你对Lambda的基本用法已经了解了,下面让我们进入主题,来研究一下什么是Kotlin的高阶函数。
1.2 通过Java代码理解Kotlin中的高阶函数
在刚开始学习用Kotlin编写Android时,我们可能会在代码中看到下面这个,对于初学者来说很不友好的代码片段:
fun example(n: Int, m: Int, func: (Int, Int) -> Int) {
print(func(n, m))
}
fun main() {
example(1, 2) { n, m -> n + m }
}
不过观察一下就会发现,它要求了一个Lambda表达式作为传入参数。在上述对Lambda表达式的讲解中,我们知道Lambda表达式其实就是一个函数。在Java中也有类似的操作:
package com.suibian.www.java;
public class LambdaDemo {
public static void main(String[] args) {
testLambda(() -> {});
}
public static void testLambda(Flyable flyable) {
System.out.println("hello");
}
}
interface Hello {
void sayHllo();
}
我们把lambda表达式作为方法的参数进行传递,但是在Java中用一个接口进行接收。而在Kotlin中,我们使用一个更加简洁(不必再声明接口)的函数参数的方式来实现。
因为Kotlin最终还是要编译为Java字节码,所以其实这两种方式得到的最终结果是相同的——实现原理都是使用匿名函数的方式。所以这意味着Kotlin将要产生额外的花销去创建该匿名类实例。
为了解决这个问题,Kotlin提供了内联函数的功能。
1.3 Kotlin中的高阶函数之内联函数
实现内联函数只需要在函数前声明inline关键字即可,
inline val result = test(m , n){n1 , n2 -> n1 + n2}
其原理是,将lambda表达式的操作替换到 test方法 ,中“作为一个正常的语句而执行”,这样就不需要在test中进行传参:
val result = test(m , n)
fun Int test(n1 : Int , n2 : Int){
return n1 + n2
}
接下来再将 test方法 中的代码全部转移到调用的位置,这样就不需要调用该方法了:
val result = n1 + n2
这样就解决了额外的开销问题,但是inline关键字会将参数中的所有Lambda表达式内联,如果想要使某个函数参数是非内联的,可以在参数前加上noinline关键字。
1.4 内联函数和非内联函数的区别
既然内联函数可以解决资源消耗的问题i,那么为什么还要使用非内联函数?
1.4.1
首先内联函数会在编译的过程中被进行代码替换,可以理解为内联函数并不是一个”真正的“参数。所以在传入了内联函数参数的方法内,该”不是真正的参数“不能被传递给其他方法。
fun difTest(test: () -> Unit) {}
inline fun inlineTest(noinline test: () -> Unit) {
difTest(test)
}
fun main() {
inlineTest { print(2) }
}
1.4.2
其次,还是因为内联函数会被代码替换,所以在Lambda表达式的代码块部分可以包括 return 语句。该 return 语句返回的是调用该内联函数的函数,因为代码都被替换到了外层函数中。
此外,无论是内联函数或非内联函数,都可以进行局部返回,相当于 return 了一段代码块:
fun printString(str: String, test: (String) -> Unit) {
println("printString begin")
test(str)
println("printString end")
}
fun main() {
println("main start")
val str = ""
printString(str) { s ->
println("lambda start")
if (s.isEmpty()) return@printString
println(s)
println("lambda end")
}
println("main end")
}
main start
printString begin
lambda start
printString end
main end
1.4.3 crossinline 关键字
来看一段会引发问题的代码:
当外层是内联函数时,我们就可以在传入test的函数参数中加入 return 语句来进行外层代码返回。但如果此时对test函数的调用是在另一个高阶函数内部(非内联函数),则该返回会变成对匿名内部类中的函数进行返回。容易造成语义冲突:
inline fun runRunnable(test: () -> Unit) {
val Runnable = Runnable {
test()
}
runnnable.run()
}
可以利用crossinline 关键字来解决:
inline fun runRunnable(crossinline test: () -> Unit) {
val Runnable = Runnable {
test()
}
runnnable.run()
}
该关键字的作用是声明该Lambda表达式一定不会使用 return 语句进行返回。
欢迎各位读者一起讨论