Kotlin基础——函数定义及调用

文章目录

  • 1 函数参数
    • 1.1 命名参数
    • 1.2 参数默认值
    • 1.3 可变参数
  • 2 顶层函数和属性
    • 2.1 顶层函数
    • 2.2 顶层属性
  • 3 扩展函数和属性
    • 3.1 扩展函数
    • 3.2 导入和使用扩展函数
    • 3.3 从Java中调用扩展函数
    • 3.4 不可重写的扩展函数
    • 3.5 扩展属性
  • 4 中缀调用和解构声明
    • 4.1 中缀调用
    • 4.2 解构声明
  • 5 字符串和正则表达式
  • 6 局部函数和扩展

1 函数参数

1.1 命名参数

fun main() {
    val list = arrayListOf(1, 2, 3)
    val result = joinToString(list, ";", "(", ")")
    println(result)
}

fun <T> joinToString(
    collection: Collection<T>,
    separator: String,
    prefix: String,
    postfix: String
): String {
    val result = StringBuilder(prefix)
    for ((index, element) in collection.withIndex()) {
        if (index > 0)
            result.append(separator)
        result.append(element)
    }
    result.append(postfix)
    return result.toString()
}

传统的方法,无法分清到底是哪个参数,只能依赖IDE提示。

joinToString(list, /*separator*/";", /*prefix*/"(", /*postfix*/")")

Java中可以通过注释来指示这个参数的含义,或者通过enum枚举类型的名称来区分
在Kotlin中则可以通过命名参数来达到这个目的

joinToString(list, separator = ";", prefix = "(", postfix = ")")

指定该参数是赋给哪个形参的

1.2 参数默认值

Kotlin中的函数支持设置参数默认值,拥有默认值的参数可以不用传参而使用默认值,也可以传参来覆盖默认值。
像上面的函数, 如果前缀和后缀只是偶尔需要改变,则可以使用默认参数

fun <T> joinToString(
    collection: Collection<T>,
    separator: String = ";",
    prefix: String = "(",
    postfix: String = ")"
): String {...}
val result = joinToString(list)

有了参数默认值之后,可以选择其中的一些参数进行赋值

val result = joinToString(list, prefix = "[")

其他的使用默认值
参数默认值只能从右往左相连的参数,否则还是需要提供显示的参数,因为会产生歧义

fun <T> joinToString(
    collection: Collection<T>,
    separator: String = ";",
    prefix: String,
    postfix: String = ")"
): String

这样编译没有问题,但是第二个参数还是默认会给separator,所以这样跳着的设置默认参数没有意义,separator的默认参数无法生效
Java中没有参数默认值,怎么调用Kotlin中的有参数默认值的函数呢?
Java中调用,需要提供所有的参数

List list = List.of(1, 2, 3);
String result = TestDemo1Kt.joinToString(list, ";", "[", "]");

要想在Kotlin中那样调用,可以在Kotlin函数上添加@JvmOverloads注解,这样就会从最右侧的参数默认值开始依次省略参数来生成重载方法。
对于这个

@JvmOverloads
fun <T> joinToString(
    collection: Collection<T>,
    separator: String = ";",
    prefix: String = "(",
    postfix: String = ")"
): String {...}

会生成

String joinToString(Collection<T> collection, String separator, String prefix, String postfix) {...}

String joinToString(Collection<T> collection, String separator, String prefix) {...}

String joinToString(Collection<T> collection, String separator) {...}

String joinToString(Collection<T> collection) {...}

这四个方法

1.3 可变参数

可变参数在Java中也支持,使用三个点表示可变参数,而Kotlin中使用vararg关键字

public fun <T> arrayListOf(vararg elements: T): ArrayList<T>

Kotlin中的arrayListOf函数就是使用的可变参数
与Java中有区别的是可变参数对于数组的支持,Java中对于可变参数,可以直接传入数组,而Kotlin中需要将数组元素展开后进行调用

public static void main(String[] args) {
    int[] arr = {1, 2, 3};
    test(arr);
}

public static void test(int... args) {
    for (int i : args) {
        System.out.println(i);
    }
}

Java中可变参数可以直接传入数组

fun main(args: Array<String>) {
    val arr = intArrayOf(1, 2, 3)
    test(*arr)
}

fun test(vararg args: Int) {
    for (i in args) {
        println(i)
    }
}

而Kotlin中可变参数则需要使用星号这个展开运算符将数组展开才能进行调用,否则编译失败。

test(4, *arr)

Kotlin中可变参数可以这样添加,而Java中不可以,因为Java中传入的数组是一个整体,传入数组就不能加其他参数了,而Kotlin中因为将数据进行了展开,所以还可以传其他参数。

2 顶层函数和属性

Kotlin中的函数和变量可以直接放到类外,称为顶层函数和顶层属性
顶层函数和属性不从属于类,但是还是从属于包,在使用时,如果不在同一个包中,需要导入

2.1 顶层函数

StringFunctions.kt

package strings
fun joinToString() {}

在Java中调用

import strings.StringFunctionsKt;

public class TestDmoe2 {
    public static void main(String[] args) {
        StringFunctionsKt.joinToString();
    }
}

Kotlin的顶层函数在编译的时候会生成一个静态函数,而从属的类就是kotlin文件名的拼接。
如果想自定义从属的类名,可以使用@file:JvmName注解来修改顶层函数所属的类

@file:JvmName("StringFunctions")
package strings
fun joinToString() {}
import strings.StringFunctions;

public class TestDmoe2 {
    public static void main(String[] args) {
        StringFunctions.joinToString();
    }
}

更改从属的类名,在Java中调用
使用顶层函数可以取代Java中的工具类写法。

2.2 顶层属性

//val count = 0
var count = 0

同类中的属性一样,顶层属性也是自动提供访问器。val类型提供getter方法,var类型提供setter和getter方法。
如果想要定义一个无法从外部访问的,可以使用const修饰

const val count = 0

这相当于

public static final int count = 0;

此时,默认不提供访问器,包括getter方法,但可以访问属性

int count = StringFunctions.count;

3 扩展函数和属性

Kotlin中可以对现有类添加方法和属性,在外部调用看来,添加的方法就像是归属于这个类一样,称为扩展函数和扩展属性

3.1 扩展函数

fun String.lastChar(): Char {
    return this[this.length - 1]
}

fun main() {
    val str = "test"
    println(str.lastChar())
}

前面类的名称称为接受者类型,后面用来调用这个函数的对象称为接受者对象。
在这个扩展函数中,可以使用this,this就是传入的接收者对象,也就是调用者对象。这种调用和调用String类的成员函数一样。
也可以像成员函数一样,省略this

return this[length - 1]

需要注意的是扩展函数不能破坏原有类的封装类,不能访问私有或保护成员,只能访问公有成员。

3.2 导入和使用扩展函数

扩展函数在其他包使用的时候,需要导入

import strings.lastChar

fun main(args: Array<String>) {
    val str = "test"
    str.lastChar()
}

如果有重名的函数,可以通过as关键字来取别名

import strings.lastChar as last

3.3 从Java中调用扩展函数

扩展函数的实质是通过将对象作为第一个参数进行传入的,所以上面的lastChar函数在Java中相当于

public static char lastChar(String str)

调用时使用

String str = "test";
StringFunctions.lastChar(str);

3.4 不可重写的扩展函数

扩展函数看起来像是String的成员函数,但是实际上并不是,他只是一个将String作为第一个参数传入的函数,是归属于StringFunctions类的,所以假设想要继承String,并重写lastChar方法,这并不是重写,而只是在子类中定义了一个lastChar方法而已。

3.5 扩展属性

由于扩展属性不是真的类中的属性,无法存储数据,所以没有合适的地方进行存储,对于扩展的属性,需要提供构造器,没有提供构造器是无法编译通过的

val String.lastChar: Char = 'c'

常规属性的这种初始化会编译失败

val String.lastChar: Char
    get() {
        return get(length - 1)
    }

没有支持字段,所以没有默认的getter方法。初始化也不可以,因为没有地方存储初始化值。

fun main() {
    val str = "test"
    println(str.lastChar)
}

接下来就可以像访问常规属性一样访问扩展属性了

StringFunctions.getLastChar("test");

而在Java中,需要显式的调用getter方法

4 中缀调用和解构声明

中缀调用和结构声明通常用于键值对的处理

4.1 中缀调用

val map = mapOf(1 to "One", 2 to "Two")

上面的to就是中缀调用,to是一个函数

public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)

这是函数实现。中缀调用可以用于任何只有一个参数的函数,包括扩展函数。在定义中缀调用的时候,使用infix修饰。

4.2 解构声明

val (num, name) = 1 to "One"

上面中缀调用将等号后面封装成一个Pair对象,这里使用解构申明将数据从Pair对象中解析处理,其中key放到了num中,value放到了name中

5 字符串和正则表达式

Kotlin中的字符串使用的是原生Java的String类,只是Api存在一些差异,Kotlin提供了一系列扩展函数,让对字符串的处理更加简单。
Java中的正则表达式可以直接使用String字符串表示,而Kotlin中需要将正则表达式封装的Regex对象中才能使用

println("12.345-6.A".split("\\.|-".toRegex()))

这避免了Java中的一些问题,比如Java中如下代码

System.out.println(Arrays.toString("12.345-6.A".split(".")));

打印的是空数组,因为会自动将点号识别为正则表达式,匹配所有字符。而在Kotlin中这样使用

println("12.345-6.A".split("."))

输出

[12, 345-6, A]

因为没有显式的将点号声明为正则表达式,所以只是匹配一个点号
除此之外,Kotlin中的扩展函数支持多个分割符

println("12.345-6.A".split(".", "-"))

在使用正则表达式时,可以使用三引号类型的字符串,在三重引号里面的符号使用其本意,不需要转义符

val path = "/User/Tom/Kotlin/hello.kt"
val regex = """(.+)/(.+)\.(.+)""".toRegex()
val matchResult = regex.matchEntire(path)
if (matchResult != null) {
    val (direcotory, filename, extention) = matchResult.destructured
    println("Dir: $direcotory, Name: $filename, Ext: $extention")
}

由于三引号符里面的字符串会识别空格换行等,所以想要做对齐的话可以添加边界符,并取消它

println("""|
          .|
          .|
""".trimMargin("."))

输出

|
|
|

6 局部函数和扩展

class User(val id: Int, val name: String, val address: String)

fun saveUser(user: User) {
    if (user.name.isEmpty()) {
        throw IllegalArgumentException(
            "can't save user ${user.id}: empty name"
        )
    }
    if (user.address.isEmpty()) {
        throw IllegalArgumentException(
            "can't save user ${user.id}: empty address"
        )
    }
}

对于上面的重复部分,在Java中可以将重复的部分抽取到一个方法中来避免重复代码,但是这样如果这段代码只在该函数中使用,抽取到方法中则暴露给外部方法了。
Kotlin支持局部函数,可以在一个函数中定义一个局部函数,该局部函数只在定义它的函数中可见,局部函数可以访问所在函数中的所有参数和变量。
使用局部函数抽取如下:

fun saveUser(user: User) {
    fun validData(value: String, fieldName: String) {
        if (value.isEmpty()) {
            throw IllegalArgumentException(
                "can't save user ${user.id}: empty $fieldName"
            )
        }
    }

    validData(user.name, "name")
    validData(user.address, "address")
}

需要注意的是局部函数的调用需在局部函数调用之后,否则找不到这个函数

你可能感兴趣的:(Kotlin语言,kotlin,windows,开发语言)