# Kotlin教程 - 9 标准库函数
Kotlin 标准库提供了丰富的函数和工具类,用于各种常见的编程任务。在前面也或多或少的用到了一些,在这里我们再针对之前没有讲到的函数进行讲解。
# 9.1 字符串函数
Kotlin 中,针对字符串提供了很多函数,下面介绍一下字符串常用的一些函数。
# 1 截取
substring
方法可以用来根据 index 来截取字符串
val str = "Hello, World!"
val substring = str.substring(7, 12) // 从索引 7 到 11 的子字符串
println(substring) // 输出 "World"
2
3
# 2 分割
split
方法可以将字符串分割成多个子字符串。
val sentence = "This is a sample sentence"
val words = sentence.split(" ") // 使用空格分割
println(words) // 输出: [This, is, a, sample, sentence]
2
3
也可以使用解构语法来接收结果:
val sentence = "This is a sample sentence"
val (one, two, three) = sentence.split(" ") // 使用空格分割
println(one) // 输出: this
println(two) // 输出: is
println(three) // 输出: a
2
3
4
5
# 3 替换
replace
方法可以替换字符串中的内容。
val sentence = "Good good study, day day up"
var str = sentence.replace("Good", "GOOD", true) // 将字符串中的 good 全部替换为 GOOD,第三个参数表示忽略大小写。
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)
}
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
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
}
2
3
4
5
6
7
8
# 5 字符串遍历
使用 forEach
函数,可以遍历字符串中每个字符。
val str = "Doubi"
str.forEach {
println(it) // 依次输出D、o、u、b、i
}
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)
}
2
3
4
5
6
7
8
9
10
在上面的例子中,apply
函数被调用在 Person
对象上,并且通过 lambda 表达式修改了 name
和 age
属性。最后,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) // 设置文件可执行
}
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)
}
}
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)
}
}
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)
}
2
3
4
5
6
如果使用 let 函数,那么可以这样写
fun main() {
val list = listOf(3, 2, 3)
val result = list.first().let { it * it } // 对第一个元素进行操作,并返回结果
println(result)
}
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)
}
2
3
4
5
可以进行链式调用。
再举一个例子:
fun main() {
val name:String? = null
val content = name?.let { "Hello $it!" } ?: "What's your name"
println(content)
}
2
3
4
5
在上面的代码中,如果 name
不为 null
,则打印 Hello xxx
,如果 name
为 null
,则打印 What's your name
。
apply 和 let 的区别:
- apply 在 lambda 表达式中使用
this
关键字引用接收者对象。通常用于对对象进行初始化或配置,返回的是操作对象本身。 - let 在 lambda 表达式中使用
it
参数引用接收者对象。通常用于对对象进行一系列操作,返回的是 lambda 表达式最后一行结果。
# 3 run
run
是 Kotlin 标准库中的一个扩展函数。它的主要目的是在对象的上下文中执行一组操作,并返回 lambda 表达式的结果。
run
函数与 apply
、 let
函数类似,但有一些区别。
run
在 lambda 表达式中使用 this
关键字引用接收者对象,与 apply
相似;但 run
返回 lambda 表达式的结果,与 let
相似。
举个栗子:
fun main() {
val username = "HelloworldImtheking";
val tooLong = username.run { length > 60 }
println(tooLong)
}
2
3
4
5
在上面的代码中,在 run
函数传递的 lambda 表达式中判断了 username
的长度并返回了结果。
run
、apply
、 let
在上面传递的是 lambda 表达式,但其实是函数,所以可以直接传递函数。
下面通过 run
来举栗子。
fun main() {
val str: String = "Doubi"
val tooLong = str.run(::tooLong)
println(tooLong)
}
fun tooLong(name: String): Boolean = name.length > 16
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 "用户名长度正确"
}
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)
}
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) // 设置文件可执行
}
}
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)
}
}
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)
}
}
2
3
4
5
6
7
8
9
10
apply
、let
、run
、also
四个函数太像了,总结一下:
函数 | 参数 | 返回 |
---|---|---|
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
}
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)
}
2
3
4
5
6
7
首先使用 takeIf
判断文件是否存在,是否可读,然后根据返回的结果确定是否执行 readText
函数,最终读取到文件的内容。
# 7 takeUnless
takeUnless
和 takeIf
的判断条件是相反的,一般直接用 takeIf 了,takeUnless用的比较少。
举个栗子:
fun main() {
val list = mutableListOf<Int>(1, 2, 3)
list.takeUnless { it.isEmpty() }?.clear()
println(list)
}
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]
}
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]
}
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]
}
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]
}
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]
}
2
3
4
5
filter
参数的 Lambda
表达式返回 true
或 false
。
# 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}
}
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
}
2
3
4
5
6
fold
的第一个参数是初始值,在该初始值上进行累加。Lambda
表达式的第一个参数是累加的结果,第二个参数为当前的元素值。
Lambda
表达式将累积值 acc
和当前元素 number
相加,并更新累积值,最终得到所有元素的累加和。