开发者社区> 行者武松> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

Kotlin函数与函数式编程浅析

简介:
+关注继续查看
Kotlin函数与函数式编程浅析

如果你对Kotlin语法一无所知,推荐先阅读官方文档或者中文站(https://www.kotlincn.net/docs/reference/)之后再看这篇文章会有更深刻的理解。本篇文章主要介绍Kotlin函数的用法,以及自己对函数式编程的一些理解。并且会和Python,C++做一些比较。

下面是维基百科上对于函数式编程的定义:

函数式编程(英语:functional programming)或称函数程序设计,又称泛函编程,是一种编程范型,它将电脑运算视为数学上的函数计算,并且避免使用程序状态以及易变对象。函数编程语言最重要的基础是λ演算(lambda calculus)。而且λ演算的函数可以接受函数当作输入(引数)和输出(传出值)。

下面是关于高阶函数的定义:

在数学和计算机科学中,高阶函数是至少满足下列一个条件的函数:接受一个或多个函数作为输入,输出一个函数

不难推断出函数式编程最重要的基础是高阶函数。也就是支持函数可以接受函数当作输入(引数)和输出(传出值)。

函数作为Kotlin中的一级公民可以像其他对象一样作为函数的输入与输出,这也就是Java程序员转到Kotlin觉得变化最大,最难理解的一点。如果你之前学过Python或者C++11可能会对此比较容易接受。这也是为什么本文以介绍Kotlin的函数及函数式编程为主。

Kotlin 函数

下面是Kotlin中一般的函数定义,和Java不同的是函数形参,返回值类型置后。函数体可以用等号赋值给函数定义,这里也可以看出函数和变量的平等性。


  1. fun main(args: Array) { 
  2.     var s = sum(1,2) 
  3.     var m = multi(2,3) 
  4.     var x = maxOf(3,4) 
  5.  
  6. fun sum(a: Int, b: Int): Int { 
  7.     return a + b 
  8.  
  9. fun multi(a: Int, b: Int): Int = a * b 
  10.  
  11. fun maxOf(a: Int, b: Int): Int = if (a > b) a else b  

另外Kotlin还支持函数默认参数,拓展函数,中缀表达式,下面是简单的例子:


  1. fun main(args: Array) { 
  2.     isBiggerThan(2) 
  3.     isBiggerThan(2, 5) 
  4.     var s = "a".isLetter() 
  5.     var a = 1 add 2 
  6.  
  7. fun isBiggerThan(a: Int, b: Int = 0) { 
  8.     return a > b 
  9.  
  10. //拓展函数 
  11. fun String.isLetter(): Boolean { 
  12.     return matches(Regex("^[a-z|A-Z]$")) 
  13.  
  14. //拓展函数,中缀表达式 
  15. infix fun Int.add(x: Int): Int { 
  16.     return this + x 
  17. }  

支持默认参数的函数可以减小函数的重载。

String对象中本没有判断是否是字母的方法,在Java中我们一般会定义一些Utils方法,而在Kotlin中可以定义类的拓展函数。

第二个例子是给Int类定义了一个拓展函数,并且该拓展函数以中缀表达式表示,给予了开发者定义类似关键字的权利。

比如我们可以这样创建一个map对象:


  1. val kv = mapOf("a" to 1, "b" to 2) 

这里的to就是一个中缀表达式,定义如下:


  1. public infix fun<A, B> A.to(that: B): Pair<A, B> = Pair(this, that) 

Pair就是Map中存的对象,所以你也可以这样创建


  1. val kv = mapOf(Pair("a", 1), Pair("b", 2)) 

在Python中如果我们想让函数返回多个值,可以返回一个元组,Kotlin基于解构原则也可以实现类似的功能:


  1. fun main(args: Array) { 
  2.     val (indexcount) = findWhere("abcabcabcabc"'c'
  3.  
  4. fun findWhere(str: String, findChar: Char): Pair<IntInt> { 
  5.     var index = -1 
  6.     var count = 0 
  7.     for ((i, v) in str.withIndex()) { 
  8.         if (v == findChar) { 
  9.             if (index == -1) { 
  10.                 index = i 
  11.             } 
  12.             ++count 
  13.         } 
  14.     } 
  15.     return Pair(indexcount
  16. }  

自定义对象如何支持解构请查看官方文档,map支持解构,所以可以像下面这样遍历:


  1. for ((k, v) in map) { 
  2.     print("$k -> $v, "
  3. }  

高阶函数与 Lambda 表达式

“Lambda 表达式”(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。Lambda表达式可以表示闭包(注意和数学传统意义上的不同)。

Python中的lambda表达式:


  1. add = lambda x, y:x+y 

C++中的lambda:


  1. [](int x, int y) -> intreturn x + y; } 

Kotlin中的lambda:


  1. var add = {x: Int, y: Int -> x + y} 

Kotlin 作为一个强类型语言还是比较简洁的。

我们可以这样使用一个lambda表达式:


  1. fun main(args: Array) { 
  2. val sumLambda = {a: Int, b: Int -> a + b} 
  3. sumLambda(1, 2) 
  4. }  

它可以像函数一样使用()调用,在kotlin中操作符是可以重载的,()操作符对应的就是类的重载函数invoke()。

你还可以想下面这样定义一个变量:


  1. val numFun: (a: Int, b: Int) -> Int 

它不是一个普通的变量,它必须指向一个函数,并且函数签名必须一致:


  1. fun main(args: Array) { 
  2.     val sumLambda = {a: Int, b: Int -> a + b} 
  3.     var numFun: (a: Int, b: Int) -> Int 
  4.     numFun = {a: Int, b: Int -> a + b} 
  5.     numFun = sumLambda 
  6.     numFun = ::sum 
  7.     numFun(1,2) 
  8.  
  9. fun sum(a: Int, b: Int): Int { 
  10.     return a + b 
  11. }  

可以看到这个变量可以等于一个lambda表达式,也可以等于另一个lambda表达式变量,还可以等于一个普通函数,但是在函数名前需要加上(::)来获取函数引用。

这个类似C++中的函数指针,然而在Python中可以直接使用函数名作为函数引用,下面是c++函数指针的例子:


  1. #include  
  2.  
  3. using namespace std; 
  4.  
  5. void swap(int &x, int &y); 
  6.  
  7. int main(int arg, char* args[]) { 
  8.     int x = 10; 
  9.     int y = 20; 
  10.  
  11.     void (*methodPtr)(int &x, int &y);//声明一个函数指针 
  12.     methodPtr = &swap; //函数指针赋值 
  13.     methodPtr = swap;//取地址符可省略,效果和上面一致 
  14.     methodPtr(x, y); //像给函数起了一个别名,可以直接使用()调用 
  15.     cout << "x:" << x << " y:" << y << endl; //x:20 y:10 
  16.  
  17. void swap(int &x, int &y) { 
  18.     int tmp = x; 
  19.     x = y; 
  20.     y = tmp; 
  21. }  

回到Kotlin,我们还可以将一个函数传递给另一个函数,比如:


  1. //函数参数 
  2. fun  doMap(list: List, function: (it: T) -> Any) { 
  3.     for (item in list) { 
  4.         function(item) 
  5.     } 
  6. }  

第一个参数是一个List,第二个参数是一个函数,目的就是将List中的每一个元素都执行一次第二个函数。使用方法如下:


  1. val strList = listOf("h" ,"e""1""a""b""2"" """"c""5""7""F"
  2. doMap(strList, {item ->print("item: ${upperLetter(item)}, ") }) 
  3.  
  4. fun upperLetter(item: String): String { 
  5.     if (item.isLetter()) { 
  6.         return item.toUpperCase() 
  7.     } 
  8.     return item 
  9. }  

第二个参数直接传进去了一个lambda表达式,当然也可以传一个函数引用:


  1. val strList = listOf("h" ,"e""1""a""b""2"" """"c""5""7""F"
  2. doMap(strList, ::printUpperLetter) 
  3.  
  4. fun printUpperLetter(item: String) { 
  5.     print("item: ${upperLetter(item)}, "
  6.  
  7. fun upperLetter(item: String): String { 
  8.     if (item.isLetter()) { 
  9.         return item.toUpperCase() 
  10.     } 
  11.     return item 
  12. }  

效果和上面的代码一样。

在C++中使用函数指针可以实现类似的效果:


  1. using namespace std; 
  2.  
  3. void mMap(vector list, void (*fun)(int item)); 
  4.  
  5. int main(int arg, char* args[]) { 
  6.     vector list = {2,3,4,3,2,1,2}; 
  7.     mMap(list, [](int item) -> void { cout << item << endl; }); 
  8.  
  9. void mMap(vector list, void (*fun)(int item)) { 
  10.     for(int it : list) { 
  11.         fun(it); 
  12.     } 
  13. }  

再次回到Kotlin,如果函数作为入参在入参列表的最后一个,你还可以这样做,直接写在大括号内:


  1. fun main(args: Array) { 
  2.     log { sum(1,2) } 
  3.  
  4. fun  log(function: () -> T) { 
  5.     val result = function() 
  6.     println("result -> $result"
  7. }  

是不是有点像gradle配置文件的写法,所以Kotlin可以很方便的编写 领域专用语言(DSL)

另外Kotlin还支持局部函数和函数作为返回值,看下面的代码:


  1. fun main(args: Array) { 
  2.     val addResult = lateAdd(2, 4) 
  3.     addResult() 
  4. //局部函数,函数引用 
  5. fun lateAdd(a: Int, b: Int): Function0 { 
  6.     fun add(): Int { 
  7.         return a + b 
  8.     } 
  9.     return ::add 
  10. }  

在lateAdd内部定义了一个局部函数,最后返回了该局部函数的引用,对结果使用()操作符拿到最终的结果,达到延迟计算的目的。

函数作为一级公民当然可以像普通对象一样放进map中,比如下面这样:


  1. val funs = mapOf("sum" to ::sum
  2. val mapFun = funs["sum"
  3. if (mapFun != null) { 
  4.    val result = mapFun(1,2) 
  5.    println("sum result -> $result"
  6.  
  7. fun sum(a: Int, b: Int): Int { 
  8.     return a + b 
  9. }  

将一个函数引用作为value放进了map中,取出来之后使用()操作符调用,可以简化一些if,else的场景。

基于以上函数式编程的特性,Kotlin可以像RxJava一样很方便的进行相应式编程,比如:


  1. fun printUpperLetter(list: List) { 
  2.     list 
  3.             .filter (fun(item):Boolean { 
  4.                 return item.isNotEmpty() 
  5.             }) 
  6.             .filter { item -> item.isNotBlank()} 
  7.             .filter { 
  8.                 item -> 
  9.                 if (item.isNullOrEmpty()) { 
  10.                     return@filter false 
  11.                 } 
  12.                 return@filter item.matches(Regex("^[a-z|A-Z]$")) 
  13.             } 
  14.             .filter { it.isLetter() } 
  15.             .map(String::toUpperCase) 
  16.             .sortedBy { it } 
  17.             .forEach { print("$it, ") } 
  18.     println() 
  19. }  

上面的代码只是做演示,并无实际意义。具体语法请查看官方文档。

我相信Kotlin作为一种强类型的现代化语言可以在保证稳定性的同时极大地提高开发者的开发效率。


作者:佚名

来源:51CTO

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
【Kotlin】Kotlin 构造函数 ( 主构造函数 | 主构造函数声明属性 | init 初始化代码块 | 次构造函数 | 构造函数委托 | 调用构造函数创建实例对象 )(一)
【Kotlin】Kotlin 构造函数 ( 主构造函数 | 主构造函数声明属性 | init 初始化代码块 | 次构造函数 | 构造函数委托 | 调用构造函数创建实例对象 )(一)
113 0
【Kotlin】Kotlin 构造函数 ( 主构造函数 | 主构造函数声明属性 | init 初始化代码块 | 次构造函数 | 构造函数委托 | 调用构造函数创建实例对象 )(二)
【Kotlin】Kotlin 构造函数 ( 主构造函数 | 主构造函数声明属性 | init 初始化代码块 | 次构造函数 | 构造函数委托 | 调用构造函数创建实例对象 )(二)
60 0
在Android Studio中使用Kotlin
在Android Studio中使用Kotlin
37 0
Android Studio 制作聊天界面实践(Kotlin版)
Android Studio 制作聊天界面实践(Kotlin版)
93 0
当Android的Kotlin出现了Unresolved reference:***(设置的控件id)
这几天学习Kotlin,在看郭霖老师的《第一行代码》第三版这本书,这里一段程序,同样得样例,自己敲会报错(入图),很疑惑,自己手动解决了。 这里出现了Unresolved reference: recyclerView,我前面activity_main.xml设置了一个id 解决这问题,要在MainActivity加入包 import kotlinx.android.synthetic.main.activity_main.* 如果kotlinx 报红了,要在app的build.gradle文件中
329 0
【Kotlin 初学者】Android Studio 运行 main 函数报错解决
目录 Andorid Studio 运行 main 函数报错: Gradle构建问题 New Project问题 运行成功 IntelliJ IDEA 其他问题 kotlin-gradle-plugin Failed to find Build Tools revision 31.0.0 解决方案1:移除buildToolsVersion版本 解决方案2:降低buildToolsVersion
156 0
使用Kotlin开发Android应用
Kotlin是一门基于JVM的编程语言,它正成长为Android开发中用于替代Java语言的继承者。Java是世界上使用最多的编程语言之一,当其他编程语言为更加便于开发者使用而不断进化时,Java并没有像预期那样及时跟进。
55 0
Kotlin + Netty 在 Android 上实现 Socket 的服务端
Kotlin + Netty 在 Android 上实现 Socket 的服务端
267 0
Android 开发中 Kotlin Coroutines 如何优雅地处理异常
Android 开发中 Kotlin Coroutines 如何优雅地处理异常
122 0
基于 Kotlin+Netty 开发的 Android Web Server
基于 Kotlin+Netty 开发的 Android Web Server
132 0
+关注
行者武松
杀人者,打虎武松也。
文章
问答
文章排行榜
最热
最新
相关电子书
更多
低代码开发师(初级)实战教程
立即下载
阿里巴巴DevOps 最佳实践手册
立即下载
冬季实战营第三期:MySQL数据库进阶实战
立即下载