在编程的世界里,字符串处理是日常开发中不可或缺的一部分。无论是拼接信息、格式化输出,还是进行复杂的文本分析,字符串操作始终占据着重要地位。然而,在传统的Java语言中,字符串的处理往往需要编写大量的代码,这不仅增加了开发的工作量,还容易引入错误。
幸运的是,Kotlin的出现为开发者带来了福音。作为一门现代化的编程语言,Kotlin在字符串处理方面进行了大幅度的改进和优化。它不仅继承了Java的字符串处理能力,还通过丰富的扩展函数为开发者提供了更加便捷、高效的操作方式。这些扩展函数就像是隐藏在字符串背后的魔法,让原本繁琐的字符串操作变得简单而优雅。 本文将深入探讨Kotlin字符串的奥秘,带您领略Kotlin如何凭借其强大的扩展函数,让字符串处理变得更加轻松有趣。无论您是Kotlin的新手还是资深开发者,相信都能从中学到新的技巧和知识,让您的编程之路更加顺畅。
开胃小菜
在开始介绍字符串使用之前,我们先引入几个很好用的关于 Char 的扩展(其实部分不是 Kotlin 特有的扩展,内部是调用了相关的 Java 函数)
isLetter
expect fun Char.isLetter(): Boolean
如果这个字符是一个字母,则返回 true。
isDigit
expect fun Char.isDigit(): Boolean(source)
如果这个字符是一个数字,则返回 true。
isWhitespace
expect fun Char.isWhitespace(): Boolean
判断一个字符是否为空白字符。
那么,哪些是空白字符呢?我们测试一下:
println('\n'.isWhitespace()) // true
println('\t'.isWhitespace()) // true
println(' '.isWhitespace()) // true
println('a'.isWhitespace()) // false
空格,换行符,制表符都会认为是空白字符。
isLowerCase
expect fun Char.isLowerCase(): Boolean
如果该字符是小写字母,则返回 true。
注意,希腊字母也分大小写:
println('A'.isLowerCase()) // false
println('a'.isLowerCase()) // true
println('λ'.isLowerCase()) // true
同理,针对字符的大小写,还有 isUpperCase 、 uppercase 、 lowercase 等关于大小写的扩展。
如果你对希腊字母大小写感兴趣,我们测试一下部分希腊字母:
println('α'.uppercase()) // Α
println('β'.uppercase()) // Β
println('γ'.uppercase()) // Γ
println('λ'.uppercase()) // Λ
进入正题——字符串
介绍完部分字符的扩展函数之后,我们来看下本文章真正的主角——字符串扩展。
isBlank
fun CharSequence.isBlank(): Boolean
如果这个字符序列为空,则返回 true。其实只要每个字符都是空,那么结果就是 true。
我们测试一下看看:
println(" ".isBlank()) // true
println(" \t\n ".isBlank()) // true
println("".isBlank()) // true
isEmpty
inline fun CharSequence.isEmpty(): Boolean
如果这个字符序列为空(即不包含任何字符),则返回 true。其实就是简单的双引号,中间没有任何内容就是 Empty。
针对 Blank,和 Empty,Kotlin 给了很多比较方便的扩展,例如:
isNotBlank、isNotEmpty、isNullOrBlank、isNullOrEmpty。开发者可以按需选择适合自己的扩展进行业务逻辑的开发。
all
inline fun CharSequence.all(predicate: (Char) -> Boolean): Boolean
如果字符序列中的所有字符都满足给定的条件,则返回 true。
请注意,如果字符序列为空,函数也会返回 true,因为空序列中没有字符不满足条件。
val str = "01234"
println(str.all { it.isDigit() }) // true
println(str.all { it.isLowerCase() }) // false
val chars = "abcdE" // E 不是小写
println(chars.all { it.isLowerCase() }) // false
any
fun CharSequence.any(): Boolean
如果字符序列中至少包含一个字符,则返回 true。emmm,其实这个扩展就是:
public fun CharSequence.any(): Boolean {
return !isEmpty()
}
当然,这个 any 还有一个需要传递条件的版本:
inline fun CharSequence.any(predicate: (Char) -> Boolean): Boolean
如果字符序列中至少有一个字符满足给定的条件,则返回 true。
val chars = "abcdE"
println(chars.any { it.isLowerCase() }) // true
println("".any { it.isLowerCase() }) // false
associate
inline fun <K, V> CharSequence.associate(transform: (Char) -> Pair<K, V>): Map<K, V>
返回一个 Map,其中的键值对是通过将给定字符序列的每个字符应用于转换函数而生成的。
如果存在键值对具有相同的键,则 Map 中将包含最后一个键值对。
生成的 Map 保持了原始字符序列中条目的顺序。
val string = "bonne journée"
// associate each character with its code
val result = string.associate { char -> char to char.code }
// notice each letter occurs only once
println(result) // {b=98, o=111, n=110, e=101, =32, j=106, u=117, r=114, é=233}
associateBy
该扩展有两个版本:
inline fun <K> CharSequence.associateBy(keySelector: (Char) -> K): Map<K, Char>
返回一个 Map,其中的键是由 keySelector 函数应用于给定字符序列中每个字符后返回的,值则是相应的字符。
如果两个字符通过 keySelector 函数返回了相同的键,那么 Map 中只会包含最后一个字符。
生成的 Map 保持了原始字符序列中条目的顺序。
val string = "bonne journée"
// associate each character by its code
val result = string.associateBy { char -> char.code }
// notice each char code occurs only once
println(result) // {98=b, 111=o, 110=n, 101=e, 32= , 106=j, 117=u, 114=r, 233=é}
第二个版本:
inline fun <K, V> CharSequence.associateBy(keySelector: (Char) -> K, valueTransform: (Char) -> V): Map<K, V>
返回一个 Map,其中的键是通过将给定字符序列的每个字符应用于 keySelector 函数而生成的,值则是通过将每个字符应用于 valueTransform 函数而生成的。
在第一个版本的基础上,就是给了一个能够生成值的 valueTransform,其它方面保持一致。
chunked
fun CharSequence.chunked(size: Int): List<String>
将字符序列分割成一个字符串列表,其中每个字符串的长度都不超过指定的 size。
在生成的列表中,最后一个字符串的长度可能会短于指定的长度。
val dnaFragment = "ATTCGCGGCCGCCA"
println(dnaFragment.chunked(3)) // [ATT, CGC, GGC, CGC, CA]
substringAfter / substringBefore
fun String.substringAfter(delimiter: Char, missingDelimiterValue: String = this): String
返回第一次出现的分隔符之后的子串。如果字符串中没有分隔符,则返回 missingDelimiterValue,其默认值为原始字符串。
fun String.substringBefore(delimiter: Char, missingDelimiterValue: String = this): String
返回第一次出现的分隔符之前的子串。如果字符串中没有分隔符,则返回 missingDelimiterValue,其默认值为原始字符串。
用法实例,After 和 Before,我们可以理解为一个向后分割,一个向前分割:
val path = "Kotlin.Main.kt"
val language = path.substringBefore('.')
val file = path.substringAfter('.')
println(file) // Main.kt
println(language) // Kotlin
substringAfterLast / substringBeforeLast
fun String.substringAfterLast(delimiter: Char, missingDelimiterValue: String = this): String
返回最后一次出现的分隔符之后的子串。如果字符串中没有分隔符,则返回 missingDelimiterValue,其默认值为原始字符串。
fun String.substringBeforeLast(delimiter: Char, missingDelimiterValue: String = this): String
返回最后一次出现的分隔符之前的子串。如果字符串中没有分隔符,则返回 missingDelimiterValue,其默认值为原始字符串。
纸上谈来终觉浅,给个例子:
val path = "Kotlin.Main.kt"
val extension = path.substringAfterLast('.')
val fileName = path.substringBeforeLast('.')
println(extension) // kt
println(fileName) // Kotlin.Main
其实就是在查找分隔符的时候,从后面往前找。
substringAfterLast方法特别适合用于分割文件后缀。
toInt / toIntOrNull / toDouble / toDoubleOrNull / toLong / toLongOrNull
字符串转换为数字类型。以 toInt 为例,其它扩展意义类似:
expect fun String.toInt(): Int
将字符串转换为 Int 类型的数值。
println("0".toInt()) // 0
println("42".toInt()) // 42
println("042".toInt()) // 42
println("-42".toInt()) // -42
注意,该方法如果无法将字符串转换为 Int,则会抛出 NumberFormatException。所以我们推荐更加安全的 toIntOrNull。
// Int.MAX_VALUE
println("2147483647".toIntOrNull()) // 2147483647
// Int 溢出
println("2147483648".toIntOrNull()) // null
// 'a' 不是数字
println("-1a".toIntOrNull()) // null
hexToInt / hexToLong / hexToShort
fun String.hexToInt(format: HexFormat = HexFormat.Default): Int
根据指定的16进制格式,将这个字符串解析为一个 Int 类型的值。
// Using the default format
println("3a".hexToInt()) // 58
// Parsing is case-insensitive
println("3A".hexToInt()) // 58
那么类似 0xccffaa 这样的16进制字符串呢?(Android的小伙伴是不是很熟悉)
// Using a custom format
val format = HexFormat { number.prefix = "0x" }
println("0xccffaa".hexToInt(format)) // 13434794
trim
inline fun String.trim(): String
返回一个删除了前导和尾随空白字符的字符串。
还有一个版本开发者可以自定义前导和尾随字符:
fun String.trim(vararg chars: Char): String
举个例子:
val a = " abc "
println(a.trim()) // abc
val b = "aabbccddaa"
println(b.trim('a')) // bbccdd
padStart / padEnd
fun CharSequence.padStart(length: Int, padChar: Char = ' '): CharSequence
返回一个字符序列,其内容为此字符序列在开始处用指定的字符或空格填充到指定的长度。
val padWithSpace = "125".padStart(5)
println("'$padWithSpace'") // ' 125'
val padWithChar = "a".padStart(5, '.')
println(padWithChar) // ....a
val padWithZero = "1".padStart(5, '0')
println(padWithZero) // 00001
padEnd 工作原理类似,只不过是从后面填充。
random
inline fun CharSequence.random(): Char
从这个字符序列中随机选择并返回一个字符。