源码地址:https://github.com/cn-ljb/KotlinBlogs

函数定义

什么是函数…不用解释了吧…

函数范围

Kotlin 中可以在文件顶级声明函数,这就意味者你不用像在Java一样创建一个类来持有函数。除了顶级函数,Kotlin 函数自然也可以声明为局部的,作为成员函数或扩展函数。

定义一个函数

一个简单的方法定义:

fun add(n1: Int, n2: Int): Int {return n1 + n2
}

基本格式:

fun 函数名(形参1: 参数类型 , 形参2: 参数类型...) <: 返回值类型>{函数体...
}

fun关键字定义

参数变量与参数类型之间用冒号隔开,多个参数逗号隔开;

无返回值的话,返回值类型可以省略;否则紧跟函数()括号之后,冒号隔开

与Java不同点:

1、形参可以设置默认值

fun add(n1: Int = 0, n2: Int = 0, n3: Int = 0): Int {return n1 + n2 + n3
}

好处:优雅的实现了Java方法重载特性

我们可以直接这样调用代码

println(add(1, 2))      //输出:3
println(add(1, 2, 3))   //输出:6

不用像Java那样重写个方法,给某个参数传个默认值或者null了。

2、指定命名参数

如果,我只想传入第1个参数和第3个参数呢?

Kotlin中提供了命名参数的形式,来传递指定的参数

println(add(1, n3 = 4))  //跳过第2个参数

我们可以直接指定形参变量名传递参数,从而准确的传递参数到对应形参上

3、Unit 无返回值

同Java void一样,Kotlin提供Unit表示无返回值,只不过一般都省略不写

fun tell(str: String): Unit {println(str)
}//Unit省略不写
fun tell(str: String) {println(str)
}

4、可变参数

类似java中的可变参数,通过vararg关键字修饰

fun addMore(vararg arr: Int): Int {var result = 0for (num in arr) {result += num}return result
}

调用

 println(addMore(1,2,3,4,5))  //15

函数的调用

同Java

函数分类

1、单表达式函数

当函数只返回单个表达式时,大括号可以省略并在 = 后面定义函数体

诺编译器能推断出表达式返回的数据类型,返回值类型也可以省略

上面的add方法,就可改为:

fun add(n1: Int = 0, n2: Int = 0, n3: Int = 0) = n1 + n2 + n3

2、成员函数

没什么好讲的,同Java定义在类成员上的函数

class Person{fun tell(){println(".......")}
}

3、局部函数

Kotlin支持函数中定义函数,内部函数可以访问外部函数的变量。

fun outFun(): Int {var n = 1fun inFun(): Int {n += 1return n}return inFun()
}

局部函数有什么用?实现闭包

什么是闭包:函数内包含子函数,并最终return子函数。

4、中缀函数

在运算符章节已经提到过,infix关键字修饰,这里不再细讲

5、泛型函数

同Java不介绍了

fun sigletonArray<T>(item: T): Array<T> {return Array<T>(1, {item})
}

6、扩展函数

在某些情况下,某个类缺少部分功能,Java中的做法可以通过继承去扩展功能,在Kotlin中可以直接通过扩展函数的形式来解决该问题。

//扩展函数,给Int类添加一个add()函数
fun Int.add(num: Int): Int {return this.plus(num)
}

那么只要是个Int对象都可以调用该函数:

val num :Int = 10
//调用我们自定义的扩展函数
println(num.add(1))

进一步探讨,难道我们真的为Int类添加了一个成员函数?

显然是不可能的,扩展函数只是Kotlin给出的一种给某个类增加功能时新的实现思路,开发人员不必再像Java一样去创建子类或者装饰类了。那它跟真正的类成员函数有什么区别吗?

我们还是通过例子探讨这个问题:我们创建Java的Person类和它的子类SuperMan来对比(是的,Kotlin语法支持直接对Java类扩展)

//Java代码
public class Person {private String name;public Person(String name) {this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}---------------------------------------------public class SuperMan extends Person {public SuperMan(String name) {super(name);}
}

我们尝试给Person和SuperMan类分别扩展个tell()函数,打印自己的名字。

//Kotlin代码进行扩展//Person类扩展tell
fun Person.tell() {println("person:" + name)
}//SuperMan类扩展方法
fun SuperMan.tell(){println("superman:" + name)
}

分别创建Person对象和SuperMan对象,调用该函数是没有任何问题的。

val per = Person("A")
val superMan = SuperMan("B")per.tell()      //输出 : person:A
superMan.tell()  //输出: superman:B

到现在一切都是正常的,假设我们有以下函数:

fun say(per: Person) {per.tell()
}

通过say()方法我们调用tell()方法,由于SuperMan是Person的子类,所以也可以传递,我们再看看结果:

say(per)        //输出:person:A
say(superMan)   //输出:person:A

What? 既然都调用的是Person扩展的tell()函数,我们明明传递的是子类对象啊。

所以从这里看出来扩展函数实际并没有真正的给类添加一个成员函数,自然也不会有父与子之间覆写的概念,只是一套机制罢了。

1、Kotlin中扩展函数是直接被静态解析的。

也就是说扩展函数在编译时期,传递的的参数类型就已经固定不能改变,它不像继承关系里的成员函数在程序运行时期还会推到出你到底是子类对象还是父类对象。

2、扩展函数只能添加功能,不能覆写功能。

如果Peron类中已有getName(),我们对它进行扩展是不行了。

//尝试通过扩展方法覆写Person的getName()
fun Person.getName():String{return "------ $name"
}

虽然Kotlin语法不会报错,但是当我们调用getName()时,是不会有任何变化的

//尝试调用被扩展覆写的方法
println(per.getName())  //依旧输出:A

以上2点,就是扩展函数与实际成员函数的区别。

7、尾递归函数

什么是伪递归?

Kotlin 支持函数式编程的尾递归。这个允许一些算法可以通过循环而不是递归解决问题,从而避免栈溢出

tailrec关键字

当函数被标记为 tailrec 时,编译器会优化递归,并用高效迅速的循环代替它。

举例:求阶乘

Java中递归如何求阶乘?

 private static long factorial(int num) {if (num == 0 || num== 1) {return 1;} else {return num * factorial(num-1);}}

上面这个方法看似是没有问题的,并且我们输入一个较小的数,阶乘也是完全能算出来的,但当我们计算10000(或者更大)的数的阶乘,Java抛出了StackOverError(栈内存溢出):

Kotlin中呢?会有同样的问题吗?

fun factorial1(num: Int): Long {if (num == 1 || num == 0) {return 1} else {return num * factorial1(num - 1)}
}

改好后,继续求10000的阶乘,发现Kotlin中同Java一样抛出来栈溢出StackOverError

怎么解决?

Kotlin中提供tailrec关键字来解决递归嵌套层次过多导致的栈异常问题

tailrec关键字使用要求:

  • 1、tailrec关键字修饰递归方法
  • 2、递归函数内部调用自身之后,不能再有代码或者运算(上面的代码不满足这一条)

显然第一条很容易满足,但第二条之前的代码在调用自身后还有乘法运算和返回数据这两步,是不满足条件的,我们尝试多加一个参数,通过该参数的引用来获取返回值。

最终修改后的代码:

tailrec fun factorial2(num: Int, end: Result) {if (num == 1 || num == 0) {return} else {end.value *= BigInteger.valueOf(num.toLong())factorial2(num - 1, end)}
}

创建了一个Result类,来接收最后的计算结果:

/*** 该类用于接收最后伪递归返回的结果*/
class Result(var value: BigInteger = BigInteger.valueOf(1L))

这时候,计算10000的阶乘,发现很快就给出了运行结果:

那么,tailrec关键字究竟是怎么做到的?我们的代码中依旧还是有嵌套调用自身这个方法N多次,为什么没有出现异常?

前面已经提到,Kotlin发现如果是tailrec关键词修饰的函数,会将其递归替换为高效的循环。
我们把编译后字节码文件反编译回来后,就会发现factorial2()函数的确被编译器用循环的形式重新实现了:

8、高阶函数

什么是高阶函数?

高阶函数就是可以接受函数作为参数的函数。

函数作为参数?

炸一听好像很神奇,我们还是通过Java代码和Kotlin代码的对比,来了解所谓的高阶函数

首先,大家不妨想一想我们在Java中是怎么传递函数的?

传递不了?是的,Java的参数不支持函数(方法)类型,但实际开发中,比如Android中为了给一个Button设置onClick事件,通常我们会创建一个匿名内部类对象来传递这个onClick方法不是吗?

也就是说Java是通过对象的形式来传递方法的,那我们开始写代码:

需求:创建一个按钮对象,该对象提供一个downClick()方法用来触发点击事件,事件的具体内容由调用者决定。

Java版:

public class ButtonJ {public void downClick(OnClick click) {System.out.println("---start---");if (click != null) {click.onClick();}System.out.println("---end---");}public interface OnClick {void onClick();}}

Kotlin版:

class Button {fun downClick(click: () -> Unit) {println("---start---")click()println("---end---")}}

调用:

//java
ButtonJ buttonJ = new ButtonJ();
//需要传个OnClick的子类对象,才能间接调用
buttonJ.downClick(new ButtonJ.OnClick() {@Overridepublic void onClick() {System.out.println("Java按钮被点击");}
});//Kotlin
val buttonK = ButtonK()
//直接传方法
buttonK.downClick(fun() {println("Kotlin按钮被点击")
})

通过上面的比对代码,想必大家对Kotlin中高阶函数有个基本认识了吧。

注:Kotlin中如果参数只有一个,并且它是一个函数,那么可以直接写成下面这种形式:

//省略写法
buttonK.downClick{println("Kotlin按钮被点击2")
}

那如果多个参数中有一个函数,又该怎么写?(假设点击事件需要个额外参数)

/*** 添加了一个额外参数,并将参数传递给点击事件处理* */
fun downClick(arg: Int, click: (Int) -> Unit) {println("---start---")click(arg)println("---end---")
}

也就是说,你只需要把传递的函数定义为最后一个参数即可,调用如下:

//增加额外参数
buttonK.downClick(666, fun(arg: Int) {println("这是你传入的参数:$arg")
})

总觉的这个fun关键字比较碍眼,能否简写?

当然是可以的,简化后:

buttonK.downClick(777, { println("这是你传入的参数:$it") })

是不是很神奇?我们直接把fun和参数定义一起干掉了,编译器既然没报错,那么这是为什么?

其实,我们在不经意间写了个Lambda表达式({…}大括号里就是lambda表达式,it是Kotlin中为了开发人员访问参数而设置的默认的形参名,也就是我们实际传入的参数)

Lambda表达式

什么是Lamdba表达式?

其实Lambda表达式就是函数,并且我们在上面的代码中已经证明过了,不是吗?

Kotlin-Android世界的一股清流-函数相关推荐

  1. Kotlin-Android世界的一股清流

    这系列文章是做什么的? 是本人学习完Kotlin编程语言后,尝试的一次总结笔记.主要记录Kotlin语言与Java语言的不同之处,所以阅读这系列文章时,可能需要先具备一些简单的Java基础. 源码地址 ...

  2. Kotlin-Android世界的一股清流-Class类

    源码地址:https://github.com/cn-ljb/KotlinBlogs 类的定义 Kotlin中的类也是使用class关键字定义 但整个类结构与Java有所不同:Kotlin中类的定义主 ...

  3. Kotlin-Android世界的一股清流-委托

    源码地址:https://github.com/cn-ljb/KotlinBlogs 委托 一.委托类 什么是委托类? 代理设计模式,在Java中实现一个简单的代理模式如下: //抽象功能 publi ...

  4. Kotlin-Android世界的一股清流-Lambda表达式

    (转载) 源码地址:https://github.com/cn-ljb/KotlinBlogs 什么是Lambda表达式 在函数的篇章里我们知道了Lambda表达式就是函数,并且也进行了证明. 这篇文 ...

  5. Kotlin-Android世界的一股清流-流程控制

    源码地址:https://github.com/cn-ljb/KotlinBlogs 流程控制语句 if语句 基本用法同Java 唯一不同点,Kotlin中没有三目运算符(a==xxx?b:c),取而 ...

  6. Kotlin-Android世界的一股清流-Package

    源码地址:https://github.com/cn-ljb/KotlinBlogs Package 命名规则(同Java) 由小写字母.下划线.数字组成,必须由小写字母或者下划线开头 行业规范,同J ...

  7. tcl 950 android 7,TCL 950测评:商务旗舰手机界的一股清流

    TCL在国内消失了三年.这三年是国内手机厂商你争我夺得最为惨烈的三年.等它杀回来的时候这个市场早已杀红了眼.而以"宛如生活"这样的品牌定调又与国内大多数的厂商的风格都不太一样,更加 ...

  8. App 界的一股清流 音视频应有尽有 完全按照 Material design 规范设计的 App

    vld 项目地址:Cuieney/vld  简介:App 界的一股清流 音视频应有尽有 完全按照 Material design 规范设计的 App 更多:作者   提 Bug    标签: andr ...

  9. Kotlin的Reified类型:怎样在函数内使用这一类型(KAD 14)

    作者:Antonio Leiva 时间:Mar 2, 2017 原文链接:https://antonioleiva.com/reified-types-kotlin/ 对于Java开发者来说,最懊恼的 ...

最新文章

  1. Java h265视频抽帧提取照片支持Window,Linux
  2. 你的企业在什么情况下需要人工智能?快来看看你需要具备哪些条件与能力吧!...
  3. android api24如何使用uri,URI API(地图调起)
  4. php配置文件修改数据库上传,请问php.ini上传文件大小限制配置修改路径在哪里?是在数据库哪里吗?表头是?...
  5. 记录 关于浏览器跨域和设置默认浏览器的问题
  6. 全实践!3天物联网安全课程不断电
  7. java pdf表单域实现_Java 创建PDF表单域 - 文本框、复选框、列表框、组合框、按钮等...
  8. 用户注意到用户计算机中千兆位网卡,为何你电脑上的千兆网卡跑不到千兆?
  9. matlab实验符号计算答案,实验7 Matlab符号计算.doc
  10. 深入浅出 — 数据分析
  11. Ubuntu安装FreeSWITCH亲测
  12. CDliux--minidwep 无线密码渗透测试
  13. vue 解决跨域问题404问题
  14. Win10 如何进入WinRE模式?
  15. iOS总体框架介绍和详尽说明
  16. 世界上最全的解酒方法
  17. Ubuntu Navicat 安装破解+解决乱码+其他问题
  18. SDL下播放声音文件
  19. 象棋里的天地炮与重炮
  20. 机器学习理论导引_第1章:预备知识1.1

热门文章

  1. 时尚唯美婚礼视频制作AE标题模板 Wedding Responsive Titles
  2. 前端开发——Vue 监听组件生命周期
  3. 充电慢、掉电快、续航短?这份电动车过冬指南请查收!
  4. 使用Windows服务启动C#桌面应用程序问题解决
  5. 【操作系统习题】假定某多道程序设计系统供用户使用的主存空间为100 KB ,磁带机2台,打印机1台
  6. 神级:程序员面试、算法研究、编程艺术、红黑树、机器学习5大经典原创系列集锦与总结
  7. 怎样判断一个数能否被7整除
  8. 巧用foxmail同步qq邮箱的通讯录
  9. Minecraft一些红石技巧(1)
  10. 开启VScode中最简单的内部浏览器 - 可以访问外网 - Browser Preview