带你踏入kotlin大门(三)|基本功_函数篇
持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第11天,点击查看活动详情
带你踏入Kotlin大门(一)|kotlin初识 - 掘金 (juejin.cn)
带你踏入kotlin大门(二)|基本功_变量篇 - 掘金 (juejin.cn)
带你踏入kotlin大门(三)|基本功_函数篇 - 掘金 (juejin.cn)
带你踏入kotlin大门(四)|基本功_逻辑控制篇 - 掘金 (juejin.cn)
前置知识
- 已学习 Kotlin 变量知识
- 有
Java
编程基础
前言
上一篇文章 我们讲述了 kotlin
中变量与 Java 中的变量的不同,同时也简单说明 kotlin
中的类型推导机制、val
修饰的变量是 引用不可变而非对象不可变 以及我们需要优先使用 val
这些知识。
本文给讲述 kotlin
中的 头等公民 ,函数。
函数
kotlin 中的函数与 Java 中的不同
在本系列开篇文章中有提到,kotlin 在兼顾 Java 规范的同时,又做了很多拓展;其中,最重要的拓展就是:kotlin 是支持部分函数式特性的。
函数式编程具有五个鲜明的特点。
1、函数是”第一等公民”
所谓”第一等公民”(first class),指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。2、只用”表达式”,不用”语句”
“表达式”(expression)是一个单纯的运算过程,总是有返回值;”语句”(statement)是执行某种操作,没有返回值。函数式编程要求,只使用表达式,不使用语句。也就是说,每一步都是单纯的运算,而且都有返回值。3、没有”副作用”
所谓”副作用”(side effect),指的是函数内部与外部互动(最典型的情况,就是修改全局变量的值),产生运算以外的其他结果。4、不修改状态
上一点已经提到,函数式编程只是返回新的值,不修改系统变量。因此,不修改变量,也是它的一个重要特点。5、引用透明性
函数程序通常还加强引用透明性,即如果提供同样的输入,那么函数总是返回同样的结果。就是说,表达式的值不依赖于可以改变值的全局状态。
kotlin 中函数式的特性在接下来的学习中,我们会逐步学习到。下面我们先来看一下,在 kotlin 中使用函数与 Java 有何不同。
//Java
public int numsAdd(int x ,int y){
return x+y;
}
上面 Java 中的函数形式大家都很熟悉了,那么在 kotlin 中,这段函数该如何写呢?
//kotlin
public fun numsAdd(x: Int ,y: Int): Int{
return x+y
}
上述的 kotlin 代码,是将 java 代码按照原来的逻辑,一点都不变的翻译而来的。但事实上,这却并不符合 kotlin 的代码特性。我们先对这段代码进行解释,后面再将其修改为符合 kotlin 特性的代码。
kotlin 中的函数有以下几个基本特性
fun
是 function 的意思,代表这是一个功能,意思是这是一个方法,一个函数。在 kotlin 中,每一个 方法 都要使用fun
来修饰。- 方法中返回值的声明,和前文类型声明一样,采用
<name>:<object>
形式,在参数括号后用:接上类型即可。若无函数类型声明,则返回 Unit 类型,效果与 Java 中的 void 函数一样。 - 参数声明也是使用
<name>:<object>
的形式;若无参数,使用空括号即可。
事实上,若按照逻辑从 Java 中翻译过来,上一段代码中的 public
应该删除。这是为什么呢?
因为 Java 中的可见性修饰符 public ,在 Kotlin 中是不必声明的,因为 kotlin 中默认修饰符就是 public,所以我们可以将上述代码更改为如下。
//kotlin
fun numsAdd(x: Int ,y: Int): Int{
return x+y
}
同时,这里附上修饰符对比表格供大家参考。
修饰符 | Java | Kotlin |
---|---|---|
public | 所有类可见 | 所有类可见(默认) |
private | 当前类可见 | 当前类可见 |
protected | 当前类、子类、同一包路径下的类可见 | 当前类、子类可见 |
default | 同一包路径下的类可见(默认) | 无 |
internal | 无 | 同一模块中的类可见 |
前面我们还说到,kotlin 是支持函数式的,那如何把这段代码 函数式 化呢?我们可以使用一个 = 来代替代码块函数体以及返回值,这种单行的表达称之为 表达式函数体
//函数化
fun numsAdd(x: Int ,y: Int): Int = x+y
同时,基于 Kotlin 的类型推导机制,我们可以把函数返回值的类型声明去掉,更改为如下形式
//函数化+类型推导
fun numsAdd(x: Int ,y: Int) = x+y
上述代码该到这里,就是妥妥的 Kotlin 风格的代码了。我们前后对照,是不是代码量比Java要少得多呢,但是其魅力远不止于此,我们继续探索学习吧!
Kotlin 中函数的类型推导不是全局的
上述的 Kotlin 代码中,我们最后一步更改是利用了 Kotlin 的类型推导功能,但是我在这里要告诉你的是,Kotlin 的类型推导不是全局的。
这是什么意思呢?何为不是全局的?我们用上述代码举一个例子,相信你很快就能明白。假设 Kotlin 的类型推导是全局的,那么当我们没将代码函数法,且我们想利用它的 类型推导特性 的时候,我们自然而然会写出如下的代码。
//假设支持全局类型推导
fun numsAdd(x: Int ,y: Int){
return x+y
}
但事与愿违,上述代码报错了。原因是类型缺失,意思是我们需要加上函数返回值的类型。可见,Kotlin 的类型推导并非那么智能,它的类型推导不是全局的。
那么,是不是只要我们使用到 函数表达式 ,Kotlin 就一定能进行类型推导了呢?非也,我们可以看下面的这段代码。
//未作声明,会报错
fun recursion(n: Int) = if (n == 0) 1 else recursion(n-1)
这段代码依旧报错,因为递归的存在,它还是无法推导出函数的返回值类型。这是由于 Kotlin 继承和支持子类型的特性,导致类型推导不够聪明。
所以,遇到这些情况,我们做以下显式的类型声明就好了。
//做显式声明,代码成功
fun recursion(n: Int): Int = if (n == 0) 1 else recursion(n-1)
那么,关于类型声明,我们该如何选择是否显式声明呢?
如果它是一个函数的参数?
必须使用
如果它是一个非表达式定义的函数?
除了返回 Unit,其他情况必须使用
如果它是一个递归函数?
必须使用
如果它是一个公有方法,且具有返回值?
建议使用,可提高代码可阅读性以及稳定性
Lambda语法
Lambda语法,事实上在 Java 中已经出现了,当我们使用 JDK 1.8 以上,就可以在 Java 中使用了。
由于Kotlin 中的 lambda 语法使用起来是有很多技巧,且具有很多高级用法以及知识,所以在这里就只做和 Java 中 lambda 的一些简单区别展示。
首先我们看一下 ,lambda 是什么?
接下来谈众所周知的 Lambda 表达式。它是推动 Java 8 发布的最重要新特性。是继泛型(
Generics
)和注解(Annotation
)以来最大的变化。使用 Lambda 表达式可以使代码变的更加简洁紧凑。让 java 也能支持简单的函数式编程。
Lambda 表达式是一个匿名函数(初步这么理解就可,实际上是不一样的),java 8 允许把函数作为参数传递进方法中。
在 Java 中,能够使用 函数式API/lambda 的条件是,对应要表达的匿名内部类,这一个类是 单抽象方法接口 。意思是,这个类是接口,且接口里面只有一个抽象方法。
我们以常用的 button
设置点击的监听方法为例,看一下 Java 中的 lambda 实现。其中,把参数简写为 v ,并使用 -> 指向函数体
//包含匿名内部类
getBinding().homeTextViewFollower.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
...
}
});
//匿名内部类变为lambda形式
getBinding().homeTextViewFollower.setOnClickListener(v -> {
...
});
同时,我们可以看到,该内部类就是 单抽象方法接口。
以上面代码为例,Kotlin 中使用 lambda
即为如下形式。由于参数只有一个,我们可以把其直接简写删除,且外层直接用 {} 表示,表明此处为 lambda 表达式。
getBinding().homeTextViewFollower.setOnClickListener{
...
}
关于 lambda 表达式,先讲到这,后续更多的内容在后续文章中会逐步讲明。
如何传入函数作为参数
Kotlin 函数式的一大特点,就是支持在函数内写函数,或者是给函数传递函数,让函数成为第一等公民。
由于时间和篇幅原因,此处暂时不讲如何设置函数的参数可传入函数,只讲如何传入函数。关于如何设置函数参数可传入参数,我们后续讲到函数的高级应用时再说。下面的代码,可以实现向函数的参数中传入bind函数。
val binding = holder.getBinding(RecyclerviewItemRankBinding::bind)
我们传入函数,用的是 :: 这个符号,其表示将 RecyclerviewItemRankBinding
这个类中的 bind
函数传入,可以很好的实现复用和传递。
同时,我们可以看一下 getBinding 这个函数的参数设计,但这里暂不做对应更多的解释。
@Suppress("UNCHECKED_CAST")
fun <VB : ViewBinding> BaseViewHolder.getBinding(bind: (View) -> VB): VB =
itemView.getTag(Int.MIN_VALUE) as? VB ?: bind(itemView).also { itemView.setTag(Int.MIN_VALUE, it) }
上述参考代码来自 CymChad/BaseRecyclerViewAdapterHelper: BRVAH:Powerful and flexible RecyclerAdapter (github.com)
好了,今天分享的内容就是这些了,大家开工大吉哈!
参考
CymChad/BaseRecyclerViewAdapterHelper: BRVAH:Powerful and flexible RecyclerAdapter (github.com)
《kotlin核心编程》
《第一行代码》(第三版)