Kotlin语法总结:函数类型和高阶函数

Posted LQS_Android

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kotlin语法总结:函数类型和高阶函数相关的知识,希望对你有一定的参考价值。

Kotlin 中每一个函数都有一个类型,称为 函数类型 ,函数类型作为一种数据类型与基本数据类型在使用场景没有区别。可以声明变量,也可以作为其他函数的参数或者其他函数的返回值使用。

函数类型

函数类型就是把函数参数列表中的参数类型保留下来,再加上箭头符号和返回类型,形式如下:
参数列表中的参数类型 -> 返回类型
举例:
 //定义计算长方形面积函数
 //函数类型 (Double, Double) -> Double
 fun rectangleArea(width: Double, height: Double): Double { ①
      return width * height
 }

 //定义计算三角形面积函数
 //函数类型 (Double, Double) -> Double
 fun triangleArea(bottom: Double, height: Double) = 0.5 * bottom * height ②


 //函数类型 ()->Unit ③
 fun sayHello() { 
     print("Hello, World")
 }

 fun main(args: Array<String>) {
    //定义一个函数类型为(Double, Double)->Double的常量,通过函数引用方式初始化
    val getArea: (Double, Double) -> Double = ::triangleArea ④
    //调用函数
    val area = getArea(50.0, 40.0) ⑤
    print(area) //1000.0
 }

解释已经在注释中,这里不再解释。

函数字面量

函数类型可以声明的变量,那么函数类型变量能够接收什么的数据呢?即函数字面量如何
表示的问题,函数字面量可以有三种表示:
①函数引用。引用到一个已经定义好的,有名字的函数。它可以作为函数字面量。
②匿名函数。没有名字的函数,即匿名函数,它也可以作为函数字面量。
③Lambda 表达式。 Lambda 表达式是一种匿名函数,可以作为函数字面量。
函数引用
函数引用,采用 双冒号加函数名 形式表示引用,即 “ :: ”。
//在函数体中定义函数,kotlin是可以的,但Java不可以
fun calculate(opr: Char): (Int, Int) -> Int {
    
    //定义加法函数
    fun add(a: Int, b: Int): Int {
      return a + b
    }
    //定义减法函数
    fun sub(a: Int, b: Int): Int {
      return a - b
    }
  
    val result: (Int, Int) -> Int = when (opr) {
        '+' -> ::add  //①加法函数引用
        '-' -> ::sub  //②减法函数引用
        '*' -> {
             //乘法匿名函数
             fun(a: Int, b: Int): Int { ③
                return (a * b)
             }
         }
        else -> { a, b -> (a / b) } //除法Lambda表达式 ④
     }

     return result
 }
 fun main(args: Array<String>) {
      val f1 = calculate('+') //⑤f1的变量类型是函数类型(Int, Int) -> Int
      println(f1(10, 5))   //调用f1变量 
      val f2 = calculate('-') //⑥f2的变量类型是函数类型(Int, Int) -> Int
      println(f2(10, 5))
      val f3 = calculate('*') //⑦f3的变量类型是函数类型(Int, Int) -> Int
      println(f3(10, 5))
      val f4 = calculate('/') //⑧f4的变量类型是函数类型(Int, Int) -> Int
      println(f4(10, 5))
 }
 
上述代码add sub是两个局部函数,在when表达式中通过函数引用调用它们,采用 双冒号加函数名 形式引用,它们的函数引用表示方式是::add和 ::sub ,它们可以作为函数字面量赋值给result 变量。
在when表达式中的乘法函数被声明为匿名函数,匿名函数不需要函数名,它是一个表达式直接
赋值给 result 变量。
在when表达式中的除法操作符“/”采用了 Lambda 表达式形式,它也可以赋值给 result 变量。
获得一个函数类型的变量之后如何使用呢?
答案是可以把它当作函数一样调用。例如代码val f1 = calculate('+')中 f1 是一个函数类型变量,事实上 f1 就是指向 add函数的。代码调用f1 函数类型变量,事实上就是在调用 add 函数。其他的变量以
此类推,不再赘述。
高阶函数:函数作为另一个函数返回值使用
可以把函数作为另一个函数的返回值使用,那么这个函数属于高阶函数。
上面的fun calculate(opr: Char): (Int, Int) -> Int { }函数的返回类型就是(Int, Int) -> Int函数类型,说明calculate是高阶函数。
 
 //定义计算长方形面积函数
 //函数类型 (Double, Double) -> Double
 fun rectangleArea(width: Double, height: Double): Double { ①
      return width * height
 }

 //定义计算三角形面积函数
 //函数类型 (Double, Double) -> Double
 fun triangleArea(bottom: Double, height: Double) = 0.5 * bottom * height ②


 fun getArea(type: String): (Double, Double) -> Double { 
     //定义函数类型变量
     var returnFunction: (Double, Double) -> Double 
    
     when (type) {
               //rect 表示长方形
               "rect" ->  returnFunction = ::rectangleArea 
                //tria 表示三角形
                else ->   returnFunction = ::triangleArea 
      }

      return returnFunction 
 }


fun main(args: Array<String>) {
    //定义area函数变量,初始化赋值,获得计算三角形面积函数
    var area: (Double, Double) -> Double = getArea("tria") 
    println("底10 高13,计算三角形面积:${area(10.0, 15.0)}") 
    
   //获得计算长方形面积函数
    area = getArea("rect") 
    println("宽10 高15,计算长方形面积:${area(10.0, 15.0)}") 
}

上述代码定义函数getArea,其返回类型是(Double, Double) -> Double, 这说明返回值是一个函数类型。声明了一个returnFunction变量,显式指定它的类型是(Double, Double) -> Double函数类型。在when表达式中,在类型typerect(即 长方形)的情况下,把rectangleArea函数引用赋值给returnFunction变量,这种赋值之所以能够成功是因为returnFunction类型是(Double, Double) -> Double函数类型。下一行else情况的代码一样不再解释。最后将returnFunction变量返回。

在main函数中调用函数getArea,返回值area是函数类型变量。area(10,15)调用函数其参数列表是(Double, Double)

上述代码运行结果如下:

底10 高15,计算三角形面积:75.0
宽10 高15,计算长方形面积:150.0

高阶函数:函数作为参数使用

作为高阶函数还可以接收另一个函数作为参数使用。下面来看一个函数作为参数使用的示
例:
//高阶函数,funcName参数是函数类型
 fun getAreaByFunc(funcName: (Double, Double) -> Double, a: Double, b: Double): Double { 
      return funcName(a, b)
 }

fun main(args: Array<String>) {
    //获得计算三角形面积函数
    var result = getAreaByFunc(::triangleArea, 10.0, 15.0) 
    println("底10 高15,计算三角形面积:$result") 
   
   //获得计算长方形面积函数
   result = getAreaByFunc(::rectangleArea, 10.0, 15.0) 
   println("宽10 高15,计算长方形面积:$result") 
}

上述代码定义函数getAreaByFunc,它的第一个参数funcName是函数类型 (Double, Double) -> Double,第二个和第三个参数都是Double类型。函数的返回值是Double类型,是计算几何图形面积。

调用函数getAreaByFunc ,给它传递的第一个参数 ::triangleArea 是函数引用,第二个参数是三角形的底边,第三个参数是三角形的高。函数的返回值result 是Double,是计算所得的三角形面积。
调用函数 getAreaByFunc ,给它传递的第一个参数 ::rectangleArea 是函数引用,第二个参数是长方形的宽,第三个参数是长方形的高。函数的返回值result 也是Double,是计算所得的长方形面积。
上述代码的运行结果如下:
底10 高15,三角形面积:75.0
宽10 高15,计算长方形面积:150.0

经过前文的介绍总结,你会发现函数类型也没有什么难理解的,与其他类型的用法一样。

内联函数

在高阶函数中参数如果是函数类型,则可以接收Lambda表达式,而Lambda表达式在编译时被编译称为一个匿名类,每次调用函数时都会创建一个对象,如果这种被函数反复调用则创建很多对象,会带来运行时额外开销。

为了解决次问题,在Kotlin中可以将这种函数声明为内联函数,内联函数在编译时不会生成函数调用代码,而是用函数体中实际代码替换每次调用函数。

自定义内联函数
Kotlin 标准库提供了很多常用的内联函数,开发人员可以自定义内联函数,但是如果函数 参数不是函数类型,不能接收Lambda 表达式,那么这种函数一般不声明为内联函数。
声明内联函数需要使用关键字inline 修饰:
 //内联函数  函数类型作为参数
  inline fun calculatePrint(funN: (Int, Int) -> Int) { 
         println("${funN(10, 5)}")
  }

 fun main(args: Array<String>) {
        calculatePrint { a, b -> a + b } 
        calculatePrint { a, b -> a - b } 
 }

使用let函数

Kotlin中一个函数参数被声明为非空类型时,也可以接收可空类型的参数,但是如果实际参数如果真的为空,可能会导致比较严重的问题。因此需要在参数传递之前判断可空参数是否为非空,这样手动判空比较麻烦,因此Kotlin提供了let函数:
 
 fun square(num: Int): Int = num * num  //函数参数是一个非空的整数

 fun main(args: Array<String>) {
        val n1: Int? = 10  //变量n1是一个可空的变量
       
        //在调用square()函数之前,需要自己进行非空判断
        if (n1 != null) { 
              println(square(n1)) 
        }
 }

自己判断一个对象非空比较麻烦。在Kotlin中任何对象都可以调用一个let函数,let函数后面尾随一个Lambda表达式,在对象非空时执行Lambda表达式中的代码,为空时则不执行。


 fun square(num: Int): Int = num * num  //函数参数是一个非空的整数

 fun main(args: Array<String>) {
        val n1: Int? = 10  //变量n1是一个可空的变量
       
       n1?.let { n -> println(square(n)) }
       //使用隐式it代替参数n,所以简化效果如下
       n1?.let { println(square(it)) }
 }
这两行代码都是使用 let 函数进行调用效果是一样的,当 n1 非空时执行 Lambda 表达式中的代码,如果n1 为空则不执行。 n1?.let { println(square(it)) } 语句是省略了参数声明,使用隐式参数it 替代参数 n

  class MyFrame(title: String) : JFrame(title) {
         init {
                  // 创建标签
                  val label = JLabel("Label")

                  // 创建Button1
                  val button1 = JButton() ①
                  button1.text = "Button1"
                  button1.toolTipText = "Button1"
                  // 注册事件监听器,监听Button1单击事件
                  button1.addActionListener { label.text = "单击Button1" } ②

                  // 创建Button2
                  val button2 = JButton().apply { ③
                           text = "Button2"
                           toolTipText = "Button2"
                           // 注册事件监听器,监听Button2单击事件
                           addActionListener { label.text = "单击Button2" }
                           // 添加Button2到内容面板
                           contentPane.add(this, BorderLayout.SOUTH)
                  } ④

                  with(contentPane) { ⑤
                           // 添加标签到内容面板
                           add(label, BorderLayout.NORTH)
                           // 添加Button1到内容面板
                           add(button1, BorderLayout.CENTER)
                           println(height)
                           println(this.width)
                  } ⑥

                  // 设置窗口大小
                  setSize(350, 120)
                  // 设置窗口可见
                  283isVisible = true
        } 
 }

fun main(args: Array<String>) {
       //创建Frame对象
      MyFrame("MyFrame")
}
 
上述代码是 Swing 的窗口, Swing Java 的用户图形界面,本示例有图形界面组件的技术细节本暂不讨论。
代码分别创建一个Label标签和两按钮对象,其中 创建并调用 Button1 的属性和函数,这传统的做法,由于多次调用同一个对象的属性或函数,可以使用with apply 函数。
代码创建并调用Button2 的属性和函数时使用了 apply 函数, apply 函数后面尾随一个Lambda表达式,需要调用的属性和函数被放到 Lambda 表达式中, Lambda 表达式中省略的对象名button2 ,例如 text = "Button2" 表达式说明调用的 button2 text 属性,apply函数中如果引用当前对象可以使用 this 关键字,例如 contentPane.add(this, BorderLayout.SOUTH)中的this就是当前对象,apply 函数是有返回值的,它的返回值就是当前对象。
如果不需要返回值可以使用 with 函数, with 函数与 apply 函数类似,代码中调用 使用with 函数, with 函数后面也尾随一个 Lambda 表达式,需要调用的属性和函数被放到Lambda 表达式中, with 函数中如果引用当前对象也是使用 this 关键字。

 

以上是关于Kotlin语法总结:函数类型和高阶函数的主要内容,如果未能解决你的问题,请参考以下文章

Android:Kotlin详细入门学习指南-高阶函数-基础语法

Kotlin——高级篇:高阶函数详解与标准的高阶函数使用

Kotlin 总结

Kotlin小知识之高阶函数

Kotlin高阶函数内联函数以及集合变换序列

Android面试Kotlin高阶之必问三连