上一篇文章介绍了Kotlin对函数的输入参数所做的增强之处,其实函数这块Kotlin还有好些重大改进,集中体现在几类特殊函数,比如泛型函数、内联函数、扩展函数、尾递归函数、高阶函数等等,因此本篇文章就对这几种特殊函数进行详细的说明。

泛型函数

函数的输入参数类型必须在定义函数时就要指定,可是有时候参数类型是不确定的,只有在函数调用时方能知晓具体类型,如此一来要怎样声明函数呢?其实在之前的文章《 Kotlin入门(4)声明与操作数组》里面,就遇到了类似的情况,当时为了采取统一的格式声明基本类型的数组对象,使用“Array<变量类型>”来声明数组对象,并通过arrayOf函数获得数组对象的初始值,具体代码如下所示:

    var int_array:Array<Int> = arrayOf<Int>(1, 2, 3)var long_array:Array<Long> = arrayOf<Long>(1, 2, 3)var float_array:Array<Float> = arrayOf<Float>(1.0f, 2.0f, 3.0f)

注意到尖括号内部指定了数组元素的类型,这正是泛型对象的写法,“Array<变量类型>”可称作泛型变量,至于arrayOf便是本文要说的泛型函数了。
定义泛型函数时,得在函数名称前面添加“<T>”,表示以T声明的参数(包括输入参数和输出参数),其参数类型必须在函数调用时指定。下面举个泛型函数的定义例子,目的是把输入的可变参数逐个拼接起来,并返回拼接后的字符串,示例代码如下:

//Kotlin允许定义全局函数,即函数可在类外面单独定义,然后其他类也能直接调用
fun <T> appendString(tag:String, vararg otherInfo: T?):String {var str:String = "$tag:"for (item in otherInfo) {str = "$str${item.toString()},"}return str
}

调用上面的泛型函数appendString,就跟调用arrayOf方法一样,只需在函数名称后面添加“<变量类型>”即可,然后输入参数照原样填写。以下是appendString函数的调用代码例子:

    var count = 0btn_vararg_generic.setOnClickListener {tv_function_result.text = when (count%3) {0 -> appendString<String>("古代的四大发明","造纸术","印刷术","火药","指南针")1 -> appendString<Int>("小于10的素数",2,3,5,7)else -> appendString<Double>("烧钱的日子",5.20,6.18,11.11,12.12)}count++}

内联函数

注意到前面定义泛型函数appendString,是把它作为一个全局函数,也就是在类外面定义,不在类内部定义。因为类的成员函数依赖于类,只有泛型类(又称模板类)才能拥有成员泛型函数,普通类是不允许定义泛型函数的,否则编译器会直接报错。不过有个例外情况,如果参数类型都是继承自某种类型,那么允许在定义函数时指定从这个基类泛化开,凡是继承自该基类的子类,都可以作为输入参数进行函数调用,反之则无法调用函数。
举个例子,Int、Float和Double都继承自Number,但是定义一个setArrayNumber(array:Array<Number>)函数,它并不接受Array<Int>或者Array<Double>的入参,如果要让该方法同时接受源自Number的数组入参,就得定义泛化自Number的泛型函数,即将<T>改为<reified T : Number>,同时在fun前面添加关键字inline,表示该函数也为内联函数。内联函数在编译之时,会在调用处把该函数的内部代码直接复制一份,调用十次就会复制十份,而非普通函数那样仅仅提供一个函数的访问地址。该例子的函数定义代码如下所示:

    //该函数不接受Array<Int>,也不接受Array<Double>,只好沦为孤家寡人fun setArrayNumber(array:Array<Number>) {var str:String = "数组元素依次排列:"for (item in array) {str = str + item.toString() + ", "}tv_function_result.text = str}//只有内联函数才可以被具体化inline fun <reified T : Number> setArrayStr(array:Array<T>) {var str:String = "数组元素依次排列:"for (item in array) {str = str + item.toString() + ", "}tv_function_result.text = str}

上面的泛型函数兼内联函数setArrayStr,定义的时候稍显麻烦,不过调用的方式没有变化,依旧在函数名称后面补充“<变量类型>”。该函数的调用代码示例如下:

    var int_array:Array<Int> = arrayOf(1, 2, 3)var float_array:Array<Float> = arrayOf(1.0f, 2.0f, 3.0f)var double_array:Array<Double> = arrayOf(11.11, 22.22, 33.33)//Kotlin进行函数调用时,要求参数类型完全匹配。所以即使Int继承自Number类,也不能调用setArrayNumber方法传送Int类型//btn_generic_number.setOnClickListener { setArrayNumber(int_array) }btn_generic_number.setOnClickListener {when (count%3) {0 -> setArrayStr<Int>(int_array)1 -> setArrayStr<Float>(float_array)else -> setArrayStr<Double>(double_array)}count++}

扩展函数

系统自带的类已经提供了许多方法,然而经常还是无法完全满足业务需求,此时开发者往往要写个工具类,比如StringUtil、DateUtil之类,来补充相关的处理功能,长此以往,工具类越来越多也越来越难以管理。
基于以上情况,Kotlin推出了扩展函数的概念,允许开发者给系统类补写新的方法,而无需另外编写额外的工具类。比如系统自带的数组Array提供了求最大值的max方法,提供了进行排序的sort方法,可是并未提供交换数组元素的方法。于是我们打算给Array增加新的交换方法,也就是添加一个扩展函数swap,与众不同的是要在函数名称前面加上“Array<Int>.”,表示该函数扩展自Array<Int>。swap函数的定义代码如下所示:

fun Array<Int>.swap(pos1: Int, pos2: Int) {val tmp = this[pos1] //this表示数组对象自身this[pos1] = this[pos2]this[pos2] = tmp
}

不过该函数的缺点是显而易见的,它声明了扩展自Array<Int>,也就意味着只能用于整型数组,不能用于包括浮点数组、双精度数组在内的其它数组对象。因此,为了增强交换函数的通用性,必须把swap改写为泛型函数,即尖括号内部使用T代替Int。改写为泛型函数的代码见下:

//扩展函数结合泛型函数,能够更好地扩展函数功能
fun <T> Array<T>.swap(pos1: Int, pos2: Int) {val tmp = this[pos1] //this表示数组对象自身this[pos1] = this[pos2]this[pos2] = tmp
}

有了扩展函数之后,数组对象可以直接调用新增的swap方法,仿佛该函数是系统自带的方法,用起来毫不费劲,真是开发者的福音。以下是swap函数的调用代码例子:

    //val array:Array<Int> = arrayOf(1, 2, 3, 4, 5)val array:Array<Double> = arrayOf(1.0, 2.0, 3.0, 4.0, 5.0)btn_function_extend.setOnClickListener {//下标为0和3的两个数组元素进行交换//array可以是整型数组,也可以是双精度数组array.swap(0, 3)setArrayStr<Double>(array)}

尾递归函数

Kotlin引入了扩展函数,还能反过来精简函数。具体地说,如果一个函数的表达式比较简单,一两行就可以搞定的话,Kotlin允许使用等号代替大括号。例如数学上计算n!的阶乘函数,5!=5*4*3*2*1,这个阶乘函数使用Kotlin代码的书写格式如下所示:

fun factorial(n:Int):Int {if (n <= 1) n else n*factorial(n-1)
}

从上看到阶乘函数类似Java中的“判断条件?取值A:取值B”三元表达式,只不过内部递归调用函数自身而已。前两篇文章提到Kotlin把函数当作一种特殊的变量类型,所以接下来也允许通过等号给函数这个特殊的变量进行赋值。下面便是使用等号改写后的阶乘函数代码:

fun factorial(n:Int):Int = if (n <= 1) n else n*factorial(n-1)

这里的阶乘函数是个普通的递归函数,Kotlin体系还存在一种特殊的递归函数,名叫尾递归函数,它指的是函数末尾的返回值重复调用了自身函数。此时要在fun前面加上关键字tailrec,告诉编译器这是个尾递归函数,则编译器会相应进行优化,从而提高程序性能。以下是个尾递归函数的声明代码例子:

//如果函数尾部递归调用自身,则可加上关键字tailrec表示这是个尾递归函数,
//此时编译器会自动优化递归,即用循环方式代替递归,从而避免栈溢出的情况。
//比如下面这个求余弦不动点的函数就是尾递归函数
tailrec fun findFixPoint(x: Double = 1.0): Double= if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))

高阶函数

前面多次提到函数被Kotlin当作特殊变量,包括函数声明采取跟变量声明一样的形式“名称:类型”,以及简化函数允许直接用等号连接函数体等等,那么本节最后讲述的则是把A函数作为B函数的输入参数,就像普通变量一样参与B函数的表达式计算。此时因为B函数的入参内嵌了A函数,故而B函数被称作高阶函数,对应的A函数则为低阶函数。
为了解释地更加清楚些,我们来看一个例子。对于一个数组对象,若想求得该数组元素的最大值,可以调用数组对象的max方法。现在有个字符串数组Array<String>,倘使调用该数组对象的max方法,返回的并非最长的字符串,而是按首字母排序在字母表最靠后的那个字符串。比如字符串数组为arrayOf("How", "do", "you", "do", "I'm   ", "Fine"),调用max方法获得的字符串为“you”,而不是长度最长的的那个字符串。
当然你也可以写个单独的函数专门判断字符串长度,然而要是哪天需要其它比较大小的算法,难道又得再写一个全新的比较函数?显然这么做的代价不菲,所以Kotlin引入了高阶函数这个秘密武器,直接把这个算法作为参数传进来,由开发者在调用高阶函数时再指定具体的算法函数。就获取数组对象的最大值而言,实现该功能框架的高阶函数代码如下所示:

//允许将函数表达式作为输入参数传进来,就形成了高阶函数,这里的greater函数就像是个变量
fun <T> maxCustom(array: Array<T>, greater: (T, T) -> Boolean): T? {var max: T? = nullfor (item in array)if (max == null || greater(item, max))max = itemreturn max
}

上面高阶函数的第二个参数就是一个函数变量,其中变量名称为greater,“(T, T)”表示该函数有两个类型为T的参数,然后低阶函数的返回值是Boolean类型。有了高阶函数的定义,再来看看如何调用这个高阶函数,调用的示例代码如下:

    var string_array:Array<String> = arrayOf("How", "do", "you", "do", "I'm   ", "Fine")btn_function_higher.setOnClickListener {tv_function_result.text = when (count%4) {//string_array.max()返回的是you0 -> "字符串数组的默认最大值为${string_array.max()}"//因为高阶函数maxCustom同时也是泛型函数,所以要在函数名称后面加上<String>1 -> "字符串数组按长度比较的最大值为${maxCustom<String>(string_array, { a, b -> a.length > b.length })}"//string_array.max()对应的高阶函数是maxCustom(string_array, { a, b -> a > b })2 -> "字符串数组的默认最大值(使用高阶函数)为${maxCustom(string_array, { a, b -> a > b })}"//因为系统可以根据string_array判断泛型函数采用了String类型,故而函数名称后面的<String>也可以省略掉else -> "字符串数组按去掉空格再比较长度的最大值为${maxCustom(string_array, { a, b -> a.trim().length > b.trim().length })}"}count++}

以上代码在调用maxCustom函数时,第二个参数被大括号包了起来,这是Lambda表达式的匿名函数写法,中间的“->”把匿名函数分为两部分,前半部分表示函数的输入参数,后半部分表示函数体。“{ a, b -> a.length > b.length }”按照规范的函数写法是下面这样的代码:

fun anonymous(a:String, b:String):Boolean {var result:Boolean = a.length > b.lengthreturn result
}

前述的高阶函数maxCustom同时结合了泛型函数的写法,其实还可以给它加上扩展函数的功能。因为该函数的目的是求数组对象的最大值,所以不妨将该函数扩展到Array<T>中去,扩展后的高阶函数代码示例如下:

fun <T> Array<T>.maxCustomize(greater: (T, T) -> Boolean): T? {var max: T? = nullfor (item in this)if (max == null || greater(item, max))max = itemreturn max
}

相对应的,maxCustomize将作为数组对象的成员函数进行调用,而非maxCustom那样把数组对象作为入参。改写后的调用代码如下所示:

    btn_function_higher.setOnClickListener {tv_function_result.text = when (count%4) {0 -> "字符串数组的默认最大值为${string_array.max()}"//下面是结合高阶函数与扩展函数的调用代码1 -> "字符串数组按长度比较的最大值为${string_array.maxCustomize({ a, b -> a.length > b.length })}"2 -> "字符串数组的默认最大值(使用高阶函数)为${string_array.maxCustomize({ a, b -> a > b })}"else -> "字符串数组按去掉空格再比较长度的最大值为${string_array.maxCustomize({ a, b -> a.trim().length > b.trim().length })}"}count++}

总结一下,本文一口气介绍了Kotlin的五个特殊函数,包括泛型函数、内联函数、扩展函数、尾递归函数、高阶函数,同时穿插说明了全局函数、简化函数和匿名函数,并通过实际应用叙述了多种函数结合起来的写法。通过本文与前面两篇文章的描述,读者应能掌握Kotlin对函数的大部分用法。

点此查看Kotlin入门教程的完整目录

__________________________________________________________________________
本文现已同步发布到微信公众号“老欧说安卓”,打开微信扫一扫下面的二维码,或者直接搜索公众号“老欧说安卓”添加关注,更快更方便地阅读技术干货。

Kotlin入门(11)江湖绝技之特殊函数相关推荐

  1. Kotlin入门教程——目录索引

    Kotlin是谷歌官方认可的Android开发语言,即将发布的Android Studio 3.0版本也会开始内置Kotlin,所以未来在App开发中Kotlin取代Java是大势所趋,就像当初And ...

  2. Kotlin入门(15)独门秘笈之特殊类

    上一篇文章介绍了Kotlin的几种开放性修饰符,以及如何从基类派生出子类,其中提到了被abstract修饰的抽象类.除了与Java共有的抽象类,Kotlin还新增了好几种特殊类,这些特殊类分别适应不同 ...

  3. Kotlin入门(30)多线程交互

    Android开发时常会遇到一些耗时的业务场景,比如后台批量处理数据.访问后端服务器接口等等,此时为了保证界面交互的及时响应,必须通过线程单独运行这些耗时任务.简单的线程可使用Thread类来启动,无 ...

  4. Kotlin入门(7)循环语句的操作

    上一篇文章介绍了简单分支与多路分支的实现,控制语句除了这两种条件分支之外,还有对循环处理的控制,那么本文接下来继续阐述Kotlin如何对循环语句进行操作. Koltin处理循环语句依旧采纳了for和w ...

  5. Cesium入门11 - Interactivity - 交互性

    Cesium入门11 - Interactivity - 交互性 Cesium中文网:http://cesiumcn.org/ | 国内快速访问:http://cesium.coinidea.com/ ...

  6. Kotlin入门(14)继承的那些事儿

    上一篇文章介绍了类对成员的声明方式与使用过程,从而初步了解了类的成员及其运用.不过早在<Kotlin入门(12)类的概貌与构造>中,提到MainActivity继承自AppCompatAc ...

  7. Kotlin入门(5)字符串及其格式化

    上一篇文章介绍了数组的声明和操作,包括字符串数组的用法.注意到Kotlin的字符串类也叫String,那么String在Java和Kotlin中的用法有哪些差异呢?这便是本文所要阐述的内容了. 首先要 ...

  8. Kotlin入门(33)运用扩展属性

    进行App开发的时候,使用震动器要在AndroidManifest.xml中加上如下权限: <!-- 震动 --><uses-permission android:name=&quo ...

  9. Kotlin入门(32)网络接口访问

    手机上的资源毕竟有限,为了获取更丰富的信息,就得到辽阔的互联网大海上冲浪.对于App自身,也要经常与服务器交互,以便获取最新的数据显示到界面上.这个客户端与服务端之间的信息交互,基本使用HTTP协议进 ...

最新文章

  1. Vue.js 渲染函数 JSX
  2. 今日头条的,顶部导航,实现分析
  3. linux samba服务器
  4. Vue3核心概念、新特性及与Vue2的区别
  5. SpringBoot (14)---日志配置(logback)
  6. 【思维】Kenken Race
  7. 光干涉波谷,有可能低于绝对零度
  8. python写病毒代码_十行 Python 代码写一个USB病毒
  9. 解析数论引论 第2章 数论函数和狄利克雷乘积
  10. 嵩天《Python数据分析与展示》实例3:Matplotlib基础图表绘制
  11. python生成三对角矩阵_块三对角矩阵python
  12. Texlive 2021安装卡在be patient解决方案
  13. 领域知识图谱采坑总结
  14. 赠书 | 《网络威胁情报技术指南》
  15. AlertManager 告警信息
  16. pfx证书转pem、crt、key
  17. php54_php5.4官方下载|
  18. Vegas中钢笔锚点工具的使用
  19. 《战争论》第六篇《防御》的主要原则
  20. 2022.12.5最新省市区json字符串

热门文章

  1. Leetcode每日一题:83.remove-duplicates-from-sorted-list(删除排序链表中的重复元素)
  2. 吴恩达机器学习 5.正则化
  3. (筆記) 如何增加SignalTap II能觀察的reg與wire數量? (SOC) (Quartus II) (SignalTap II)
  4. python qt gui快速编程 pdf_翻译:《用python和Qt进行GUI编程》——介绍
  5. oracle 清除参数,IMp回去的时候要把原来的表的记录清空吗?没有什么参数可以省略这个吗...
  6. 用户修改了信息jwt服务器怎么识别,django使用JWT保存用户登录信息
  7. apch连接mysql数据库连接_配置phpmyadmin连接远程 MySQL数据库
  8. 基于Chrome浏览器的前端调试
  9. java webpack web项目_零基础如何学习web前端,入门教程分享
  10. php连贯操作,Thinkphp 3.2.3 sql的一些连贯操作方法