# Kotlin教程 - 9 标准库函数

Kotlin 标准库提供了丰富的函数和工具类,用于各种常见的编程任务。在前面也或多或少的用到了一些,在这里我们再针对之前没有讲到的函数进行讲解。

# 9.1 字符串函数

Kotlin 中,针对字符串提供了很多函数,下面介绍一下字符串常用的一些函数。

# 1 截取

substring 方法可以用来根据 index 来截取字符串

val str = "Hello, World!"
val substring = str.substring(7, 12) // 从索引 7 到 11 的子字符串
println(substring) // 输出 "World"
1
2
3

# 2 分割

split 方法可以将字符串分割成多个子字符串。

val sentence = "This is a sample sentence"
val words = sentence.split(" ")     // 使用空格分割
println(words) // 输出: [This, is, a, sample, sentence]
1
2
3

也可以使用解构语法来接收结果:

val sentence = "This is a sample sentence"
val (one, two, three) = sentence.split(" ")     // 使用空格分割
println(one) // 输出: this
println(two) // 输出: is
println(three) // 输出: a
1
2
3
4
5

# 3 替换

replace 方法可以替换字符串中的内容。

val sentence = "Good good study, day day up"
var str = sentence.replace("Good", "GOOD", true)		// 将字符串中的 good 全部替换为 GOOD,第三个参数表示忽略大小写。
1
2

需要注意,返回的是一个新字符串,原来的字符是不受影响的。

我们也可以使用正则表达式进行匹配和替换。

fun main() {
    val sentence = "Good good study, day day up"
    val str = sentence.replace(Regex("[aeiou]")) {		// 正则表达式匹配字符串中的aeiou字符
        when (it.value) {		// 正对匹配的字符进行替换
            "a" -> "1"
            "e" -> "2"
            "i" -> "3"
            "o" -> "4"
            "u" -> "5"
            else -> it.value		// 如果不是匹配的字符,则保持原样。
        }
    }

    println(sentence)
    println(str)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

执行结果:

Good good study, day day up G44d g44d st5dy, d1y d1y 5p

关于正则表达式,请专门去学习!

# 4 ==和===

在 Kotlin 中,用 == 检查两个字符串中的字符是否匹配,用 === 检查两个变量是否指向内存上的同一个对象。

在 Java 中用 equals 检查两个字符串的字符是否匹配,用 == 做引用比较,这里区别比较大。

val str1 = "DOUBI"
val str2 = "DOUBI"

println(str1 == str2)    // true,内容相同
println(str1 === str2)   // true
1
2
3
4
5

str1 === str2 为什么为 true,因为在 JVM 中维护者一个字符串常量池,当创建了两个字符串的时候,是指向常量池中的同一个字符串。

如果中间经过一层转换,就可以看到效果:

fun main() {
    val str1 = "DOUBI"
    val str2 = "doubi".uppercase()		// 转换为大写字母,此处会重新创建一个字符串DOUBI

    println("${str1},${str2}")	// DOUBI,DOUBI
    println(str1 == str2)    		// true,内容相同
    println(str1 === str2)   		// false
}
1
2
3
4
5
6
7
8

# 5 字符串遍历

使用 forEach 函数,可以遍历字符串中每个字符。

val str = "Doubi"
str.forEach {
    println(it)		// 依次输出D、o、u、b、i
}
1
2
3
4

# 9.2 扩展函数

# 1 apply

apply 函数可看做一个配置函数,这个函数接收一个 lambda 表达式作为参数,该 lambda 表达式是接收者对象类型的扩展函数,通常用于对接收者对象进行一系列初始化或配置操作。在 lambda 表达式中,可以通过 this 关键字引用接收者对象。

举个栗子:

data class Person(var name: String, var age: Int)

fun main() {
    val person = Person("John", 30).apply {
        name = "Jane"
        age = 25
    }

    println(person) // 输出: Person(name=Jane, age=25)
}
1
2
3
4
5
6
7
8
9
10

在上面的例子中,apply 函数被调用在 Person 对象上,并且通过 lambda 表达式修改了 nameage 属性。最后,apply 函数返回修改后的对象。可以直接在 apply 函数的 lambda 表达式中对对象进行操作,不用使用变量名。

再举一个栗子:

如果不使用 apply 函数,我们要配置一个文件的属性,应该这样配置:

import java.io.File

fun main() {
    var file = File("E:\\text.txt")
    file.setReadable(true)		// 设置文件可读
    file.setWritable(true)		// 设置文件可写
    file.setExecutable(true)	// 设置文件可执行
}
1
2
3
4
5
6
7
8

通过 apply 函数来配置的话,可以这样写:

import java.io.File

fun main() {
    var file = File("E:\\text.txt").apply {
        this.setReadable(true)		// 可以使用 this
        setWritable(true)
        setExecutable(true)
    }
}
1
2
3
4
5
6
7
8
9

同样,可以直接在 apply 函数的 lambda 表达式中对对象进行操作,不用使用变量名,多个函数组成链式调用在 lambda 表达式中,可以通过 this 关键字引用接收者对象,当然也可以直接调用。

也可以多次调用 apply 进行多次的链式调用:

import java.io.File

fun main() {
    var file = File("E:\\text.txt").apply {
        setReadable(true)		
    }.apply {
        setWritable(true)
    }.apply {
        setExecutable(true)
    }
}
1
2
3
4
5
6
7
8
9
10
11

在上面多次调用了 apply 函数,因为每次 apply 函数返回的是对象本身。

# 2 let

前面已经介绍过,这里再学习一下。

let 是 Kotlin 标准库中的另一个扩展函数。它的主要目的是在对象的上下文中执行一组操作,并返回 lambda 表达式的结果。

举个栗子:

如果要实现取列表中第一个 元素的值,求这个值的平方,不使用let,可能是下面的写法:

fun main() {
    val list = listOf(3, 2, 3)
    val firstElement = list.first()						// 取出第一个元素
    val result = firstElement * firstElement	// 求平方
    println(result)
}
1
2
3
4
5
6

如果使用 let 函数,那么可以这样写

fun main() {
    val list = listOf(3, 2, 3)
    val result = list.first().let { it * it }		// 对第一个元素进行操作,并返回结果
    println(result)
}
1
2
3
4
5

通过 let 函数对第一个元素进行操作,并可以返回结果。简洁很多。

我们甚至可以很方便的将得到的平方的结果再求平方:

fun main() {
    val list = listOf(3, 2, 3)
    val result = list.first().let { it * it }.let { it * it }		// 可以进行链式调用
    println(result)
}
1
2
3
4
5

可以进行链式调用。

再举一个例子:

fun main() {
    val name:String? = null
    val content = name?.let { "Hello $it!" } ?: "What's your name"
    println(content)
}
1
2
3
4
5

在上面的代码中,如果 name 不为 null ,则打印 Hello xxx ,如果 namenull ,则打印 What's your name

apply 和 let 的区别:

  • apply 在 lambda 表达式中使用 this 关键字引用接收者对象。通常用于对对象进行初始化或配置,返回的是操作对象本身。
  • let 在 lambda 表达式中使用 it 参数引用接收者对象。通常用于对对象进行一系列操作,返回的是 lambda 表达式最后一行结果。

# 3 run

run 是 Kotlin 标准库中的一个扩展函数。它的主要目的是在对象的上下文中执行一组操作,并返回 lambda 表达式的结果。

run 函数与 applylet 函数类似,但有一些区别。

run 在 lambda 表达式中使用 this 关键字引用接收者对象,与 apply 相似;但 run 返回 lambda 表达式的结果,与 let 相似。

举个栗子:

fun main() {
    val username = "HelloworldImtheking";
    val tooLong = username.run { length > 60 }
    println(tooLong)
}
1
2
3
4
5

在上面的代码中,在 run 函数传递的 lambda 表达式中判断了 username 的长度并返回了结果。

runapplylet 在上面传递的是 lambda 表达式,但其实是函数,所以可以直接传递函数。

下面通过 run 来举栗子。

fun main() {
    val str: String = "Doubi"
    val tooLong = str.run(::tooLong)
    println(tooLong)
}

fun tooLong(name: String): Boolean = name.length > 16
1
2
3
4
5
6
7

在上面的代码中,为 run 函数传递了 tooLong 函数的引用,最终得到执行的结果。

同样,也可以使用链式调用,执行多个函数:

fun main() {
    val str: String = "Doubi"
    str.run(::tooLong).run(::getMessage).run(::println)
}

fun tooLong(name: String): Boolean = name.length > 16

fun getMessage(tooLong: Boolean): String {
    return if (tooLong)  "用户名太长" else "用户名长度正确"
}
1
2
3
4
5
6
7
8
9
10

在上面的代码中,tooLong 函数的执行结果传递给了 getMessage 函数,getMessage 函数的结果传递给了 println,最终打印出来。

# 4 with

with 函数是 run 的变体,他们的功能是一样的,只是 with 的调用方式不同,需要将值作为第一个参数传递。

举个例子:

fun main() {
    val str: String = "Doubi"
    val tooLong = with(str) { length > 16}
    println(tooLong)
}
1
2
3
4
5

同样也可以进行类似 apply 的链式调用:

import java.io.File

fun main() {
    var file = File("E:\\text.txt")
    with(file) {
        setReadable(true)		// 设置文件可读
        setWritable(true)		// 设置文件可写
        setExecutable(true)	// 设置文件可执行
    }
}
1
2
3
4
5
6
7
8
9
10

一般情况下,我们可能会优先使用 run 函数。

# 5 also

also 函数好 let 函数功能类似,但是 also 返回的是对象本身,这个是 apply 函数是一样的。

举个栗子:

fun main() {
    val str: String = "Doubi"

    str.also {
        println(it)
    } .also {
        val content = "Hello ${it}!"
        println(content)
    }
}
1
2
3
4
5
6
7
8
9
10

在上面的代码中, also 表达式的参数是 it ,返回的是对象本身,不是 lambda 表达式的结果。

如果使用 apply 的话,是这样写的:

fun main() {
    val str: String = "Doubi"

    str.apply {
        println(this)
    } .apply {
        val content = "Hello ${this}!"
        println(content)
    }
}
1
2
3
4
5
6
7
8
9
10

applyletrunalso 四个函数太像了,总结一下:

函数 参数 返回
apply this 对象本身
let it lambda表达式最后结果
run this lambda表达式最后结果
also it 对象本身

卧槽,搁这有点排列组合呢。

# 6 takeIf

takeIf 需要传递一个 lambda 表达式,表达式执行的结果需要是 true 或 false,如果表达式结果为 true,则返回对象本身,如果为 false,则返回 null。

举个栗子:

fun main() {
    val number = 10
    val result = number.takeIf { it > 5 }
    println(result) // 输出: 10
}
1
2
3
4
5

在上面的代码中,如果 number 大于5,则返回 number,如果 number 不大于5,则返回 null

如果需要判断某个条件是否满足,在决定是否可以赋值变量或执行某项任务,takeIf 就很有用了,它类似于 if 语句,可以直接在对象上调用,避免创建临时变量。

再举个例子:

我们在读取文件内容的时候,首先判断文件是否存在,如果存在就读取,否则返回 null。

import java.io.File

fun main() {
    var file = File("E:/text.txt")
    val content = file.takeIf { it.exists() && it.canRead() }?.readText()
    println(content)
}
1
2
3
4
5
6
7

首先使用 takeIf 判断文件是否存在,是否可读,然后根据返回的结果确定是否执行 readText 函数,最终读取到文件的内容。

# 7 takeUnless

takeUnlesstakeIf 的判断条件是相反的,一般直接用 takeIf 了,takeUnless用的比较少。

举个栗子:

fun main() {
    val list = mutableListOf<Int>(1, 2, 3)
    list.takeUnless { it.isEmpty() }?.clear()
    println(list)
}
1
2
3
4
5

在上面的代码中,it.isEmpty() 返回 false,所以 takeUnless 返回 list 本身,然后执行了 clear() 函数将 list 清空。

takeUnless 容易把自己绕进去,建议使用 takeIf

# 9.3 高阶函数

什么是高阶函数,其实就是函数作为参数或返回值的函数,也就是函数式编程的具体使用。

Kotlin 提供了很多的内置高阶函数,通常有三种作用:变换(transform)、过滤(filter)、合并(combine)。

而且这些函数一般都是作用域集合,而且可以组合使用的,形成链式调用,通过组合来构建复杂的计算行为。

# 1 变换

变换函数会遍历集合,然后通过函数来操作每一个元素,然后返回修改后的元素,形成一个新的集合。

最常用的函数就是 map 和 flatMap。

# map

map 函数就是会遍历集合中各个元素,返回包含已经修改的元素的新的集合,同时还可以作为函数的输入,输入到下一个函数。

举个栗子:

假设有一个整数列表,想将这个列表中的每个数乘以2并生成一个新的列表,你可以使用map函数:

fun main() {
    val numbers = listOf(1, 2, 3, 4, 5)
    // 调用map函数
    val doubledNumbers = numbers.map { it * 2 }
    println(doubledNumbers)     // [2, 4, 6, 8, 10]
}
1
2
3
4
5
6

在上面的代码中,it 代表集合中的每个元素,然后使用 Lambda 表达式对每个元素执行乘以2的操作,返回一个新的集合,元集合不变。

还可以进行链式调用。

举个栗子:求各个元素的长度,然后将长度乘以2。

fun main() {
    val colors = listOf("red", "blue", "green", "yellow")
    // 调用map函数
    val doubleLengths = colors
        .map { it.length }      // 求每个元素的长度
        .map { it * 2 }        // 将长度求平方

    println(doubleLengths)     // [6, 8, 10, 12]
}
1
2
3
4
5
6
7
8
9

在上面使用了 map 函数进行了链式调用。可以看出返回的新集合的元素可以和原来的结合数据类型不不同。

# flatMap

flatMap 函数操作的是一个集合的集合,也就是集合中的元素也是集合,然后将多个集合的元素合并成包含所有元素的单一集合。

将集合 [[1, 2 ,3],[4,5,6]] 转换成 [1,2,3,4,5,6]

举个栗子:

fun main() {
    val numbers = listOf(listOf(1, 2, 3), listOf(4, 5, 6))
    val newNumbers = numbers.flatMap {
        println(it)         // 这里是集合
        it
    }     // 直接返回元素
    println(newNumbers)     // [1, 2, 3, 4, 5, 6]
}
1
2
3
4
5
6
7
8

在上面的代码中,将集合的集合展平,变成单一的集合。需要注意,flatMap 中的 it 是集合,也就是说 Lambda 表达式需要返回一个集合。

执行结果:

[1, 2, 3] [4, 5, 6] [1, 2, 3, 4, 5, 6]

再举个栗子:

将多个英文的语句,拆分成单词。

fun main() {
    val sentences = listOf("How are you", "I'm fine", "Thank you")
    val words = sentences.flatMap { it.split(" ") }     //
    println(words)      // [How, are, you, I'm, fine, Thank, you]
}
1
2
3
4
5

在上面的代码中,首先是集合调用 flatMap 函数,然后 Lambda 表达式需要返回集合。在 Lambda 表示中对每个句子执行 split 函数,将句子按空格拆分为单词,然后将所有单词展开为一个新的单词列表。

# 2 过滤

过滤函数接收一个 predicate 函数,也就是一个判断函数,通过条件依次判断集合中的元素是否满足条件,如果满足,则返回true。最终将所有满足条件的元素组成一个新的集合。

最常用的函数就是 filter。

# filter

filter 是用于集合过滤的高阶函数之一。它允许你根据指定的条件过滤集合中的元素,并返回满足条件的新集合。

举个栗子:

假设有一个整数列表,筛选出这个列表中所有的偶数。

fun main() {
    val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    val evenNumbers = numbers.filter { it % 2 == 0 }
    println(evenNumbers)        // [2, 4, 6, 8, 10]
}
1
2
3
4
5

filter 参数的 Lambda 表达式返回 truefalse

# 3 合并

合并函数是将不同的集合合并成一个新的集合。

最常用的函数有 zip、fold。

# zip

zip 函数用于将两个集合合并成一个 Pair 列表,第一个集合中的元素作为 key,第二个集合中的元素作为 value。

举个栗子:

fun main() {
    val fruits = listOf("Apple", "Banana", "Orange")
    val prices = listOf(1.5, 2.0, 1.8, 8.3)

    val combined = fruits.zip(prices)       // 类型:List<Pair<String, Double>>
    println(combined)   // [(Apple, 1.5), (Banana, 2.0), (Orange, 1.8)]

    val map = combined.toMap()                  // 类型Map<String, Double>
    println(map)        // {Apple=1.5, Banana=2.0, Orange=1.8}

}
1
2
3
4
5
6
7
8
9
10
11

上面通过两个集合合并成一个新的集合,一个作为 key,一个作为 value,如果两个集合的元素个数不一致,以元素少的为基准。

可以将合并的结果通过 toMap() 函数转换为 Map

# fold

fold 函数是一种集合操作函数,它允许你从集合的初始值开始,对集合中的元素进行累积操作,最终返回一个结果。

举个栗子:

假设有一个整数列表,你想对这个列表中的所有元素进行累加求和。

fun main() {
    val numbers = listOf(1, 2, 3, 4, 5)

    val sum = numbers.fold(0) { acc, number -> acc + number }
    println(sum)    // 15
}
1
2
3
4
5
6

fold 的第一个参数是初始值,在该初始值上进行累加。Lambda 表达式的第一个参数是累加的结果,第二个参数为当前的元素值。

Lambda 表达式将累积值 acc 和当前元素 number 相加,并更新累积值,最终得到所有元素的累加和。