写在开头
上一篇介绍了Kotlin的基本使用,发现这门语言主要还是面向函数进行编程,所以这一篇主要在函数方面介绍Kotlin。
基本函数
Kotlin作为一个面向函数的编程语言,函数的使用自然是最基本的,上一篇的main函数就是函数式语言的体现。最简单的调用函数的方法如下:
fun main(vararg arg: String){
println(add(1, 2))
}
fun add(a: Int, b: Int): Int{
return a + b
}
由上面的自定义函数add可知,函数的定义方式是fun <函数名>(<参数>)[: <返回类型>] { <代码块>},特别指出kotlin函数的返回类型如上面的add所示在参数后面添加表示,如果没有返回值的话可以不用写或者可以把Int换成Unit。当函数代码块中代码就是返回值就像add,可以直接写成单一表达式,如下。
fun add(a: Int, b: Int) = a + b
再来说一下参数,Kotlin中的参数使用Pascal符号定义,参数与定义变量常量不同,必须指定类型。这一点毫无疑问与java相同,但与java相同的是kotlin函数参数可以有默认值,当该参数有默认值时调用处该参不是必传的,不传就是默认值,这就是kotlin中变相的函数重载。
fun main(vararg arg: String){
println(add(1))
}
fun add(a: Int, b: Int = 5) = a + b
Lambda表达式
Kotlin中支持并使用了大量的Lambda表达式,谷歌使用Kotlin作为Android开发的一个官方语言据说也是因为能够使用这个表达式(手动滑稽)。java也在jdk8之后支持了Lambda表达式(λ),先以java为例说明。
λ表达式其实就是匿名函数在java中可以说是内部匿名类,一般语法是(参数)->{程序主体}。在java中能够作为λ表达式传入的一种形式就是函数式接口,函数式接口是只有一个抽象方法的接口,所以Runnable接口就是符合这么一个特性的可以使用λ表达式,它的源码如下:
public interface Runnable {
public abstract void run();
}
一般情况下在代码中新建一个线程是新建一个Thread实例,而Thread其中的一个构造函数参数就是Runnable,常见的代码是这样的:
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("This is a new thread!");
}
});
thread.start();
其中run方法没有参数,写成λ表达式是这个样子的:
Thread thread = new Thread(() -> System.out.println("THis is a new thread!"));
thread.start();
可以看到代码量有了明显的缩减,这也是Lambda表达式的优势。在Kotlin中是这么使用这个表达式的呢,这里依然使用add函数表示,也是这个函数的又一种变形。
var add = {a: Int, b: Int -> a + b}
println(add(1, 2))
这里输出的同样是1和2的和,一些和Lambda有关的内容可以参考细说Lambda表达式这篇文章。
一些高阶函数
高阶函数其实是将函数作为参数或者返回值的一类函数,Kotlin官方文档对其有一部分介绍——高阶函数与Lambda,这里介绍一下几个高阶函数,首先是内置的集合操作类函数。
map和flatmap
熟悉rxjava的话,应该会对这两个单词比较熟悉,map是一个集合映射成一个新集合
val list1 = listOf(1,2,3,4,5)
list1.map {
it + 1
}.forEach {
print("$it ")
}
上面的代码有一个list1集合,使用map映射的功能对集合内的元素分别进行加1,最后输出的是“2 3 4 5 6 ”,中间的空格是输出的时候添加的。而flatmap的做的事情可能要更多点。
val list1 = listOf(1,2,3,4,5)
val list2 = listOf(2,3,4,5,6)
val list3 = arrayListOf(list1, list2)
list3.flatMap { it + 1 }.forEach { print(it) }
这里的输出是1234561234561,可以看到flatmap可以将各个iterator打通成一个list。
reduce和fold
reduce是规约fold是折叠,两者都是将一个集合根据一个规则返回一个最终结果,两者的使用也是非常相似。reduce和上面的map有着千丝万缕的联系,Hadoop中使用的MapReduce计算框架可以说这里的两个函数的意思差不多。而reduce在Kotlin中是怎么使用的呢,如果我们要对map那一小节中的list1进行求和就可以使用reduce。
list1.reduce { sum, i -> sum + i }
可以看到reduce的Lambda表达式使用时有两个参数,从reduce的部分源码中可以看到第一个参数是每次的返回值也是最终的返回值,这里最后的结果是15。
public inline fun <S, T: S> Iterable<T>.reduce(operation: (acc: S, T) -> S): S {
而fold与reduce的不同之处在于他有个初始值,也就是sum可以设定一个初始值,而reduce中的sum初始值是0。
public inline fun <T, R> Iterable<T>.fold(initial: R, operation: (acc: R, T) -> R): R {
这样的化fold的使用就是如下这样的。
list1.fold(2){
sum, i-> sum + i
}
复合函数
其实这种函数的例子网上还是听常见的,这里的例子也是照搬一下网上的资源吧,只是做一下比较通俗的解释。对于这样一个数学公式:m(x)=f(g(x)),如果使用代码实现可能会直接想到其中一个函数调用另个函数的结果就可以实现,但是在Kotlin中给我们提供了函数的扩展,可以写成如下的形式。(如果是初学者看不懂也不要着急)
val add = {a: Int -> a + 5}
val multiply = {a: Int -> a * 2}
val addAndMultiply = add addThen multiply
infix fun <P1, P2, R> Function1<P1, P2>.addThen(function: Function1<P2, R>): Function1<P1, R>{
return fun(p1: P1): R{
return function(this(p1))
}
}
ok,首先定义了两个函数add和multiply分别代表了g(x)和f(x),而addAndMultiply竟然是使用了已经定义好的这两个函数,是怎么做到的,那就要看定义的这个扩展函数addThen了。
- infix关键字是Kotlin里面中缀表达式的意思,这样定义就可以像案例中用中缀的形式使两个函数结合,如果没有的化那只能是add.addThen(mutiply)了。
- P1, P2, R相当于定义使用参数的类型,其实整个函数想表达的就是P1->P2->R简化成P1->R.
- Funcion1,Kotlin提供了 Lambda表达式的函数表示,而且允许的最大入参N是22,也就是FunctionN最大是Function22,这里的add函数只有一个参数,所以这里是Function1
- addThen在这里就是Function1的扩展函数,既然是“add addThen multiply”这样使用了,那它就是add的扩展函数。
如果像下面这样变一下型,估计可以更好的理解一下其中的原理。
val add :Function1<Int, Int> = {a: Int -> a + 5}
val multiply: Function1<Int, Int> = {a: Int -> a * 2}
其实add和multiply两个函数同样是Function1类型的有一个参数,也就解释通为什么可以像上面那样定义一个扩展函数了。最后说明一下addThen扩展函数是先运行它前面的,也就是add里面的逻辑,然后运行multiply中的,即 g(x) addThen f(x) = f(g(0))。
总结
本篇写了一些Kotlin中的函数定义和使用,而且本片文章没有什么图片(因为是第一次使用markdown来写,还不顺手),继续记录自己的技术之旅。