美文网首页
Kotlin之高阶函数

Kotlin之高阶函数

作者: SunnyDay_ab5f | 来源:发表于2023-05-15 15:35 被阅读0次

一、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

相关文章

网友评论

      本文标题:Kotlin之高阶函数

      本文链接:https://www.haomeiwen.com/subject/emiaddtx.html