Kotlin之高阶函数
一、Kotlin基础函数
1 单表达式函数
当函数返回单个表达式时,可以省略花括号
例如:
fun sum(x: Int, y: Int): Int {
return x + y
}
等价于:
fun sum(x: Int, y: Int): Int = x + y
等价于:
//编译器可以推断出该函数的返回类型
fun sum(x: Int, y: Int) = x + y
2 尾递归函数
例如使用递归对自然数求和:
fun sum(n: Int, result: Int): Int = if (n<=0) result else sum(n-1,result+n)
执行上述代码,会出现StackOverflowError错误。
这是因为在kotlin中使用尾递归函数,需要满足两个条件
- 使用
tailrec
关键词修饰函数 - 在函数最后进行递归调用
修改后的代码:
tailrec fun sum(n: Int, result: Int): Int = if (n<=0) result else sum(n-1,result+n)
二、高阶函数
kotlin中的函数是"第一等公民",函数就是对象
,这个是kotlin作为函数式编程语言的重要特性。对象可以直接赋值给变量、可以作为某个函数的参数、也可以作为别的参数的返回值,那么函数也可以。
1 函数赋值给变量
1.1 函数类型
例如:
val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
等价于:
val sum: (Int, Int) -> Int = { x, y-> x + y }
其中sum: (Int, Int)
声明了入参参数类型,所以大括号中的x,y可以省略参数类型。
等价于:
val sum = { x: Int, y: Int -> x + y}
前面的函数类型变量省略了入参和返回值的类型,所以在大括号中必须声明参数类型
2 函数作为其他函数的参数
例如:
//函数类型
val identity = { x: Int -> x }
val square = { x: Int -> x * x }
val cube = { x: Int -> x * x * x }
fun sum(a: Int, b: Int, term: (Int) -> Int): Int {
var sum = 0;
for (i in a..b) {
sum += term(i)
}
return sum;
}
//调用
sum(1,10,identity)
sum(1,10,square)
sum(1,10,cube)
当然也可以直接传入一个lambda表达式
sum(1, 10, { x: Int -> x })
sum(1, 10, { x: Int -> x * x })
sum(1, 10, { x: Int -> x * x * x })
由于sum函数中的函数类型参数term在最后,所以可以将函数类型提取到外面,例如:
sum(1, 10) { x: Int -> x }
sum(1, 10) { x: Int -> x * x }
sum(1, 10) { x: Int -> x * x * x }
又因为term的入参只有一个所以可以忽略参数声明和->,使用it来代替参数,例如:
sum(1, 10) { it }
sum(1, 10) { it * it }
sum(1, 10) { it * it * it }
如果函数只有函数类型参数一个参数时括号也可以去掉如下:
fun sum(term: (Int) -> Int): Int {
var sum = 0;
for (i in 1..10) {
sum += term(i)
}
return sum;
}
sum { it * it * it }
3函数作为其他函数的返回值
//(Int, Int) -> Int 返回值是符合两个Int类型入参和Int类型的返回值的函数或lambda表达式
fun sum(type: String): (Int, Int) -> Int {
val identity = { x: Int -> x }
val square = { x: Int -> x * x }
val cube = { x: Int -> x * x * x }
return when (type) {
"identity" -> { x, y -> sum(x, y, identity) }
"square" -> { x, y -> sum(x, y, square) }
"cube" -> { x, y -> sum(x, y, cube) }
else -> { x, y -> sum(x, y, identity) }
}
}
var identityFun = sum("identity")
var value1 = identityFun(1, 10)
var squareFun = sum("square")
var value2 = squareFun(1, 10)
var cubeFun = sum("cube")
var vlaue3 = cubeFun(1, 10);
上面的实例代码也可以这样写,例如:
4 方法引用::
方法引用是简化版的Lambda表达式,它和Lambda表达式有相同的特性。方法引用不需要提供函数体,可以直接通过方法名称引用已有方法,因此,方法应用进一步简化了Lambda的写法。
var user1 = User("a")
var user2 = User("b")
var user3 = User("b")
var list = listOf(user1, user2, user3)
Collections.sort(list) {
u1,u2->u1.name.compareTo(u2.name)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Collections.sort(list,Comparator.comparing(User::name))
}
另外一个例子
private fun test(a: Int, func: (Int) -> Int) {
println(func(a))
}
private fun add(a:Int):Int{
return a+a
}
fun main() {
test(3,::add)
}
5 内联函数inline
使用inline修饰的函数称为内联函数,普通函数不需要用inline修饰这个对性能没有什么提升,inline一般使用在高阶函数中,也就是使用lambda表达式作为入参的函数中。那么使用inline都有哪些方面的提升呢,看如下代码:
函数声明
private fun test(a: Int,func:(Int)->Int){
println(func(a))
}
函数调用
fun main() {
test(3) { a: Int ->
a + a
}
}
转换成java代码看下
private final void test(int a, Function1 func) {
int var3 = ((Number)func.invoke(a)).intValue();
System.out.println(var3);
}
public final void main() {
this.test(3, (Function1)null.INSTANCE);
}
可以看到test函数中的函数类型入参被转换成了Function1对象,且在main方法中调用test函数。
那么我们把test方法加上inline关键字看下java代码是怎样的
private final void test(int a, Function1 func) {
int $i$f$test = 0;
int var4 = ((Number)func.invoke(a)).intValue();
System.out.println(var4);
}
public final void main() {
int a$iv = 3;
int $i$f$test = false;
int var5 = false;
int var4 = a$iv + a$iv;
System.out.println(var4);
}
可以看到main方法并没有调用test方法,而是把test方法中的代码复制到了main方法中直接执行,这样可以显而易见的看到两个好处:
- (1)减少了因为使用高阶函数而产生的对象创建
- (2)减少了一层方法栈的调用
6 拓展函数
拓展函数(类名.方法名())
可以拓展原有类的功能,但是方法名和参数如果与类中原有方法一样则会调用类原有的方法。
例如:
fun String.upcase():String{
return this.uppercase();
}
private fun main() {
println("aa".upcase())
}
转换为Java代码:
@NotNull
public final String upcase(@NotNull String $this$upcase) {
Intrinsics.checkNotNullParameter($this$upcase, "$this$upcase");
String var10000 = $this$upcase.toUpperCase(Locale.ROOT);
Intrinsics.checkNotNullExpressionValue(var10000, "this as java.lang.String).toUpperCase(Locale.ROOT)");
return var10000;
}
private final void main() {
String var1 = this.upcase("aa");
System.out.println(var1);
}
从上面的java代码可以看出拓展函数并不是在String类中添加新的函数,而是生成了一个工具方法进行拓展。
7 中缀函数(infix )
中缀表达式就是操作符在中间,比较符合人的阅读习惯,就像a+b一样一目了然。kotlin的中缀函数是标有infix关键字修饰的函数,常见的如to,until,step,into等。
写一个自己的中缀表达式必要条件是
1.只能有一个参数
2.必须是成员函数或拓展函数
3.参数不能是可变参数或默认参数
class Money{
var cost = 10
infix fun add(amount:Int){
this.cost = cost + amount
}
}
fun main(){
val money = Money()
money add 242
println(money.cost)
}
//输出252