带你踏入kotlin大门(三)|基本功_函数篇

简介: 本文给讲述 kotlin 中的 头等公民 ,函数。

前置知识

  • Java 编程基础
  • 已学习 Kotlin 变量知识

前言

上一篇文章 我们讲述了 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 中的函数有以下几个基本特性

  1. funfunction 的意思,代表这是一个功能,意思是这是一个方法,一个函数。在 kotlin 中,每一个 方法 都要使用 fun 来修饰。
  2. 方法中返回值的声明,和前文类型声明一样,采用 <name>:<object> 形式,在参数括号后用:接上类型即可。若无函数类型声明,则返回 Unit 类型,效果与 Java 中的 void 函数一样。
  3. 参数声明也是使用  <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 的类型推导并非那么智能,它的类型推导不是全局的

1.webp.jpg

那么,是不是只要我们使用到 函数表达式 ,Kotlin 就一定能进行类型推导了呢?非也,我们可以看下面的这段代码。

//未作声明,会报错
fun recursion(n: Int) = if (n == 0) 1 else recursion(n-1)
复制代码

1.webp.jpg

这段代码依旧报错,因为递归的存在,它还是无法推导出函数的返回值类型。这是由于 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 -> {
    ...
});
复制代码

1.webp.jpg

同时,我们可以看到,该内部类就是 单抽象方法接口

以上面代码为例,Kotlin 中使用 lambda 即为如下形式。由于参数只有一个,我们可以把其直接简写删除,且外层直接用 {} 表示,表明此处为 lambda 表达式。

getBinding().homeTextViewFollower.setOnClickListener{
    ...
}
复制代码

关于 lambda 表达式,先讲到这,后续更多的内容在后续文章中会逐步讲明。

如何传入函数作为参数

Kotlin 函数式的一大特点,就是支持在函数内写函数,或者是给函数传递函数,让函数成为第一等公民。

由于时间和篇幅原因,此处暂时不讲如何设置函数的参数可传入函数,只讲如何传入函数。关于如何设置函数参数可传入参数,我们后续讲到函数的高级应用时再说。下面的代码,可以实现向函数的参数中传入bind函数

val binding = holder.getBinding(RecyclerviewItemRankBinding::bind)
复制代码

我们传入函数,用的是 :: 这个符号,其表示将 RecyclerviewItemRankBinding 这个类中的 bind 函数传入,可以很好的实现复用和传递。

1.webp.jpg

同时,我们可以看一下 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)



相关文章
|
8月前
|
API Kotlin
Kotlin中扩展函数、infix关键字、apply函数和DSL的详解
Kotlin中扩展函数、infix关键字、apply函数和DSL的详解
75 0
|
8月前
|
安全 Java Kotlin
Kotlin中空安全操作符,异常处理和自定义异常,以及先决条件函数详解
Kotlin中空安全操作符,异常处理和自定义异常,以及先决条件函数详解
84 0
|
8月前
|
Java 编译器 Kotlin
Kotlin 中变量,类型,表达式,函数详解
Kotlin 中变量,类型,表达式,函数详解
68 0
|
6月前
|
存储 Java 编译器
Kotlin 学习笔记(四)—— 作用域函数、inline 关键字、反引号等 Kotlin 基本用法(下)
Kotlin 学习笔记(四)—— 作用域函数、inline 关键字、反引号等 Kotlin 基本用法(下)
32 0
|
6月前
|
Java Android开发 开发者
Kotlin 学习笔记(四)—— 作用域函数、inline 关键字、反引号等 Kotlin 基本用法(上)
Kotlin 学习笔记(四)—— 作用域函数、inline 关键字、反引号等 Kotlin 基本用法(上)
44 0
|
12月前
|
Java Kotlin
Kotlin data数据类、copy()函数、sealed密封类
Kotlin data数据类、copy()函数、sealed密封类使用
112 0
|
8月前
|
安全 Java 编译器
Kotlin 学习笔记(一)—— 基本类型、函数、lambda、类与对象的写法
Kotlin 作为 Android 开发的首选语言,其基础语法和特性需要重点学习。本文概括总结了 Kotlin 的基本类型、关键字、函数、闭包、高阶函数、类与对象等核心知识点,并给出示例代码进行讲解。
150 0
Kotlin 学习笔记(一)—— 基本类型、函数、lambda、类与对象的写法
|
8月前
|
Java Kotlin
Kotlin中与Java互操作与可空性、类型映射、属性访问、@JvmOverloads、@JvmField、@JvmStatic、@Throws和函数类型操作详解
Kotlin中与Java互操作与可空性、类型映射、属性访问、@JvmOverloads、@JvmField、@JvmStatic、@Throws和函数类型操作详解
68 0
|
8月前
|
Kotlin
Kotlin中标准库函数(apply、let、run、with、also、takeIf、takeUnless)的使用详解
Kotlin中标准库函数(apply、let、run、with、also、takeIf、takeUnless)的使用详解
55 0
|
9月前
|
XML Java Android开发
Kotlin作用域函数let、with、run、apply、also
Kotlin作用域函数let、with、run、apply、also
65 0