一、声明高阶函数
高阶函数定义:高阶函数就是以另一个函数作为参数或者返回值的函数。 在kotlin中,函数可以用lambda或者函数引用来表示。因此,任何以lambda或者函数引用作为参数的函数,或者返回值为lamda或函数应用的函数,或者两者都满足的函数都是高阶函数。
1、函数类型
为了声明一个以lamda作为实参的函数,你需要知道如何声明对应形参的类型。在这之前,我们先来看一个简单的例子,把lambda表达式保存在局部变量中。其实我们已经见过在不声明类型的情况下如何做到这一点,这依赖于kotlin的类型推导。
在这个例子中,编译器推导出sum和action这两个变量具有函数类型。现在我们来看看这些变量的显式类型声明是什么样的:
声明函数类型,需要将函数参数类型放在括号中,紧接着是一个箭头和函数的返回类型。
你应该还记得,Unit类型用于表示函数不返回任何有用的值。在声明一个普通的函数时,Unit类型的值是可以省略的,但是一个函数类型声明总是需要一个显式的返回类型,所以在这种场景下Unit是不能省略的。
注意,在lambda表达式{x, y -> x + y}中是如何省略参数x, y的类型的。因为它们的类型已经在函数类型的变量声明部分指定了,不需要在lamda本身的定义当中再重复声明。
就像其他方法一样,函数类型的返回值也可以标记为可空类型:
也可以定义一个函数类型的可空变量。为了明确表示是变量本身可空,而不是函数类型的返回类型可空,你需要将整个函数类型的定义包含在括号内并在括号后面添加一个问号:
注意这个例子和前一个例子的微妙区别。如果省略了括号,声明的将会是一个返回值可空的函数类型,而不是一个可空的函数的变量。
2、调用作为参数的函数
知道了怎样声明一个高阶函数,现在我们来讨论如何去实现它。
调用作为参数的函数和调用普通函数的语法是一样的:把括号放在函数名后,并把参数放在口号内。
来看一个更有趣的例子,我们来实现最常用的标准库函数:filter函数。为了让事情简单一点,将实现基于String类型的filter函数,但和作用于集合的泛型版本的原理是相似的。函数声明如下:
filter函数以一个判断式作为参数。判断式的类型是一个函数,以字符作为参数并返回boolean类型的值。如果要让传递给判断式的字符出现在最终返回的字符串中,判断式需要返回true,反之返回false。下面是具体实现:
filter函数的实现非常明了。它检查每个字符是否满足判断式,如果满足就将字符添加到包含结果的StringBuilder中。
3、在java中使用函数类
其背后的原理是,函数类型被声明为普通的接口:一个函数类型的变量是FunctionN接口的一个实现。kotlin标准库定义了一系列的接口,这些接口对应于不同参数数量的函数:Function0<R>(没有参数的函数),Function1<P1,R>(一个参数的函数),等待。每个接口定义了一个invoke方法,调用这个方法就会执行函数。一个函数类型的变量就是实现了对应的FunctionN接口的实现类的实例,实现类的invoke方法包含了lambda函数体。
在java中可以很简单地调用函数类型的kotlin函数。java8的lambda会被自动转换为函数类型的值。
在java中可以很容易地使用kotlin中以lambda作为参数的扩展函数。但是要注意他们看起来并没有kotlin中那么直观——必须要显示地传递一个接收者对象作为第一个参数:
在java中,函数或者lambda可以返回Unit。但因为在kotlin中Unit类型是有一个值的,所以需要显式地返回它。一个返回返回void的lambda不能作为返回Unit的函数类型的实参,就像之前的例子中(String)-> Unit。
4、函数类型的参数默认值和null值
声明函数类型的参数的时候可以指定参数的默认值。要知道默认值的用处,我们回头看下第三章讨论的joinToString函数。
注意这是一个泛型函数:它有一个类型参数T表示集合中的元素的类型。Lambda transform函数将接受这个类型的参数。
声明函数类型的默认值并不需要特殊的语法——只需要把lambda作为值放在=号后面。 上面的例子展示了不同的函数调用方式:省略整个lambda(使用默认的toString()做转换),在括号以外传递lambda,或者以命名参数形式传递。
另一种选择是声明一个参数为可空的函数类型。注意这里不能直接调用作为参数传递进来的函数:kotlin会因为检测到潜在的空指针异常而导致编译失败。一种可选的方法时显式地检查null。
还有一个更简单的版本,它利用了这样一个事实,函数类型是一个包含invoke方法的接口的具体实现。作为一个普通方法,invoke可以通过安全调用语法被调用:callback?.invoke()。
下面介绍使用这项技术重写joinToString函数。
现在你已经知道了如何编写接收另外的函数作为参数的函数。
5、返回函数的函数