Kotlin 知识梳理(2) - 函数的定义与调用

Kotlin 知识梳理系列文章

Kotlin 知识梳理(1) - Kotlin 基础
Kotlin 知识梳理(2) - 函数的定义与调用
Kotlin 知识梳理(3) - 类、对象和接口
Kotlin 知识梳理(4) - 数据类、类委托 及 object 关键字
Kotlin 知识梳理(5) - lambda 表达式和成员引用
Kotlin 知识梳理(6) - Kotlin 的可空性
Kotlin 知识梳理(7) - Kotlin 的类型系统
Kotlin 知识梳理(8) - 运算符重载及其他约定
Kotlin 知识梳理(9) - 委托属性
Kotlin 知识梳理(10) - 高阶函数:Lambda 作为形参或返回值
Kotlin 知识梳理(11) - 内联函数
Kotlin 知识梳理(12) - 泛型类型参数


一、本文概要

本文是对<<Kotlin in Action>>的学习笔记,如果需要运行相应的代码可以访问在线环境 try.kotlinlang.org,这部分的思维导图为:

二、在 kotlin 中创建集合

kotlin中,创建HashSetArrayListHashMap的方法如下:


通过打印这些集合的类型,可以看到是采用的标准的Java集合类:

这么做的原因,是因为使用标准的Java集合使kotlin可以更容易地与Java代码交互。当从Kotlin调用Java函数的时候,不用转换它的集合类来匹配Java的类,反之亦然。

在这些集合对象上,我们除了可以使用Java当中定义的基本函数以外,还可以使用kotlin提供的扩展方法,例如下面的lastmax


运行结果为:

三、让函数更好调用

下面,我们定义一个函数,它的作用的是打印集合当中的元素,并指定元素之间添加分隔符、前缀和后缀:



运行结果为:


3.1 命名参数

在上面的例子中,我们指定了分隔符、前缀和后缀三个参数,但是对于这个函数的使用者来说,如果不去看这些函数的声明,很难看出这些String类型的含义,这时候就可以使用 命名参数 的方法来调用,这有两点好处:

  • 增加函数的可读性
  • 以想要的顺序指定需要的参数

下面,我们使用命名参数,并在不改变函数定义的情况下,改变传入参数的顺序,也可以得到和上面相同的运行结果:


对于命名参数,有以下几点需要注意:

  • 如果在调用一个函数时,指明了一个参数的名称,那它之后的所有参数都要表明名称。
  • 当调用Java函数时,不能使用命名参数。

3.2 默认参数值

kotlin中,可以在声明函数的时候,指定参数的默认值,这样就可以避免创建重载的函数,例如上面的例子,我们可以在 定义函数 时,指定三个String的默认值,而在 调用函数 的时候,如果没有传递这些参数,那么将会采用默认值:


运行结果为:

但是要注意:

  • 如果使用常规的调用语法时,必须按照函数声明中定义的参数顺序来给定参数,可以省略的只有排在末尾的参数。
  • 如果使用命名参数,可以省略中间的一些参数,也可以以你想要的任意顺序只给定你需要的参数,例如我们只修改前缀和后缀,分隔符仍然采用默认值:



    运行结果为:


3.3 顶层函数和属性

Java中,所有的代码都需要写作类的函数,但是在项目中,很多代码并不能归属到类中,这时候我们一般会定义一个xxUtils类,并在其中声明static的静态方法。

kotlin中,我们可以把这些函数直接放到代码文件的顶层,不用从属于任何的类,这些放在文件顶层的函数仍然是包内的成员,如果你需要从包外访问它,则需要import,但不再需要额外包一层。

3.3.1 在 Java 中调用顶层函数

如果我们想要在Java中调用这些顶层函数,则需要通过Kotlin根据包含函数的文件的名称生成的类,例如我们有两个文件

  • KotlinMethod.kt
fun kotlinFunc() {}
  • JavaCallKotlin.java
public class JavaCallKotlin {
    public JavaCallKotlin() {
        KotlinMethodKt.kotlinFunc();
    }
}

前者包含一个顶层函数,那么在Java中,就会根据该文件名生成一个{文件名}Kt的类型,再通过这个类来调用这个顶层函数。

如果不想使用默认的类名,可以在.kt文件的开头加上@file:JvmName("类名")的注解。

3.3.2 在 Java 中调用顶层属性

和函数一样,属性也可以放到文件的顶层。默认情况下,顶层属性和其他任意的属性一样,是通过访问器暴露给Java使用的(如果是val就只有一个getter,如果是var就对应gettersetter)。如果想要把一个常量以public static final的属性暴露给Java,可以使用const来修饰它。

package com.demo.lizejun.kotlinsample.chapter1

//不可变。
val kotlinVal = "kotlinValue"
//可变。
var kotlinVar = "kotlinVariable"
//常量。
const val kotlinConst = "kotlinConst"
//顶层函数。
fun kotlinFunc() {}

Java中,分别通过以下几种方式来访问或者修改这几个顶层属性:

package com.demo.lizejun.kotlinsample.chapter3;

import com.demo.lizejun.kotlinsample.chapter1.KotlinMethodKt;

public class JavaCallKotlin {
    public JavaCallKotlin() {
        KotlinMethodKt.kotlinFunc();
        //不可变。
        KotlinMethodKt.getKotlinVal();
        //可变。
        KotlinMethodKt.setKotlinVar("newKotlinVar");
        KotlinMethodKt.getKotlinVar();
        //常量。
        String kotlinConst = KotlinMethodKt.kotlinConst;
    }
}

四、扩展函数和属性

扩展函数 其实是一个类的成员函数,只不过它定义在类的外面,我们所需要做的,就是在声明扩展函数的时候,把需要扩展的类或者接口的名称,放到它的前面,用来调用这个扩展函数的对象,就叫做 接收者对象

在扩展函数中,可以直接访问被扩展的类的其它方法和属性,就好像是在这个类自己的方法中访问它们的一样,但是扩展函数不允许你打破它的封装性,扩展函数不能访问私有的或者是受保护的成员。

4.1 扩展函数的定义和使用

下面我们给String类添加一个扩展函数,返回它的最后一个字符:


运行结果为:

4.2 在 Java 中使用扩展函数

如果需要在Java中调用扩展函数,那么把接收者对象作为第一个参数传进去即可:

//接收者对象作为第一个参数。
char lastChar = KotlinMethodKt.last("Kotlin");

4.3 不能重写的扩展函数

这里假设我们有两个类,ViewButton,其中Button继承于View,我们给这两个类都添加一个名为showOff的扩展函数。


运行结果为:

尽管实际上这个变量是一个Button的对象,但是Kotlin会把扩展函数当做静态函数来对待,因此 扩展函数不存在重写

4.4 扩展属性

扩展属性提供了一种方法,用来扩展类的API,可以用来访问属性,用的是属性语法而不是函数的语法,尽管他们被称为属性,但它们没有任何状态,因为没有合适的地方来存储它们。

现在,我们给StringBuilder添加一个可读写的属性lastChar,用于获取或者改变它的最后一个字符,包含以下几点要素:

  • 扩展属性以var/val关键字开头
  • 指定扩展属性的名字、类型
  • 如果是var那么提供get()/set(value : T)方法,而如果是val属性,那么提供get()方法,其中T为属性的类型。

运行结果为:


五、可变参数、中缀调用和库的支持

5.1 可变参数

使用关键字 vararg,可以用来声明一个函数将有可能有任意数量的参数,下面例子中,我们定义一个可以接收可变数量Int类型的函数,之后和在Java中一样,它会被转换为[I的整型数组类型:


运行结果为:

而如果我们已经将参数打包成一个数组,这时候如果想要将它传递给一个接收可变参数的函数,那么需要先通过*操作符进行解包:

运行结果为:

5.2 中缀调用

中缀调用 不是特殊的内置结构,而是一种特殊的函数调用。在中缀调用中,没有添加额外的分隔符,函数名称是直接放在目标对象名称和参数之间。例如我们声明了一个to函数。

//一般函数调用。
1.to("One")
//中缀调用。
1 to "One"

中缀调用可以与 只有一个参数的函数 一起使用,无论是普通的函数还是扩展函数,要允许使用中缀符号调用函数,需要使用infix修饰符来标记它,下面是一个创建Person的函数,我们采用了扩展函数的方法,这里的AnyKotlin中所有类的父类,和Java中的Object相同:


运行的结果为:

六、字符串处理

6.1 分割字符串

Java中,我们会使用split来分割字符串,它接受一个正则表达式作为参数。但是当我们想分割"."时,会得到一个空的数组,因为.号表示任何字符的正则表达式。
而在kotlin中,它提供了一个接受Regex类型的重载函数,这样确保了当有一个字符串传递给这些函数的时候,不会被当做正则表达式,我们可以使用扩展函数toRegex将字符串转换为正则表达式。


运行结果为:

6.2 正则表达式

假设有下面这个字符串

/Users/yole/kotlin-book/chapter.adoc

我们需要通过这个字符串获取到chapter.adoc的目录、文件名和扩展名,如果使用扩展函数,那么代码如下:


运行结果为:

下面是使用正则表达式的做法:

这个正则表达式将一个路径分为三个由斜线和点分隔的组:

  • .模式从字符串的一开始就进行匹配,所以第一组(.+)包含最后一个斜线之前的子串,这和子串包含所有前面的斜线,因为它们匹配”任何字符“的模式。
  • 第二组包含最后一个点之前的子串
  • 第三组包含剩余部分

七、局部函数

Java的一个函数当中,有可能存在重复代码,例如在注册模块中,可能需要校验输入的多个字段是否有效,那么校验的逻辑就可以提取出一个函数,而Kotlin就提供了一种方法:可以在函数中嵌套这些提取的函数,局部函数定义方式和普通函数是相同的。


运行的结果为:


更多文章,欢迎访问我的 Android 知识梳理系列:

推荐阅读更多精彩内容