07. 数值类和字符串详解
数值类
和 Java 一样,Kotlin 中的所有数字类型都是有符号的(signed),也就是说,它们既可以表示正数,也可以表示负数。 除了是否支持小数外,数字类型还有个区别是在内存中所占的位数(直接的结果就是它们所能支持的最大值和最小值)。
整数是不带小数位的数字,在 Kotlin 中常用 Int 类型表示。带小数位的数字要以 Float 或 Double 类型表示。
这里有必要提一下 Short 和 Byte 这两个类型。千言万语汇成一句话,你几乎不会用它们来表示常见的数。它们主要用于和 Java 遗留代码互操作这样的场景。例如,从文件读取数据流 或处理图像时(1 个颜色像素常以 3 字节表示,对应 RGB 三色),你可能就需要和 Byte 打交道。 而需要和不支持 32 位指令的 CPU 原生代码交互时,你很可能会看到 Short 的身影。不管怎么说,大多数情况下,表示整数都用 Int,如果需要更大的数,那么就用 Long。
数字的字面常数
在 Java 中表示 long 类型整数时可以在数字后面加小写英文字母 l,但由于可读性不好,容易被误认为是阿拉伯数字 1,所以在 Kotlin 中部不允许这样表示。
在使用整数变量赋值,还可以使用二进制和十六进制表示,但不支持八进制,它们的表示方式分别如下:
- 二进制数:以 0b 或 0B 为前缀,注意 0 是阿拉伯数字,不要误认为是英文字母 o。
- 十六进制数:以 0x 或 0X 为前缀,注意 0 是阿拉伯数字。
可以使用下划线使数字常量更易读:
1 | val oneMillion = 1_000_000 |
显式数字转换
在 Kotlin 中,没有隐式的数字扩大转换。例如,带有 Double 参数的函数只能在 Double 值上调用,而不能在 Float、 Int 或其他数值上调用。
1 | fun main() { |
kotlin 提供了可以把所有数字类型(包括字符串)转换为包括数在内的其他类型值的显式数字转换。
- toByte(): Byte
- toShort(): Short
- toInt(): Int
- toLong(): Long
- toFloat(): Float
- toDouble(): Double
在许多情况下,不需要显式转换,因为类型是从上下文推断出来的,并且对于适当的转换,算术操作是重载的,例如:
1 | val l = 1L + 3 // Long + Int => Long |
尝试转换格式错误的字符串会抛出异常。例如,调用 toInt 函数转换字符串 “5.91” 就会抛出异常,因为字符串的 .91 部分无法转成 Int 值。
考虑到这个问题,Kotlin 提供了 toDoubleOrNull 和 toIntOrNull 这样的安全转换函数。 如果数值不能正确转换,与其触发异常不如干脆返回 null 值。另外,调用 toIntOrNull 函数时, 可以同时使用空合并操作符。例如,你可以提供一个默认值:
1 | val gold: Int = "5.91".toIntOrNull() ?: 0 |
Double 类型格式化
要格式化的值作为值参传给 format 函数。Kotlin 使用的格式化字符串和 Java、C/C++、Ruby 等其他语言中的标准格式化字符串是一 样的。想了解更多格式化字符串形式和规则,参见 Java API 文档:https://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html。
1 | val d = 1.234567 |
无符号整数类型
除了整数类型之外,Kotlin 还为无符号整数提供了以下类型:
- UByte: 一个无符号的8位整数,范围从 0 到 255
- UShort: 一个无符号的16位整数,范围从 0 到 65535
- UInt: 一个无符号的32位整数,范围从 0 到 2 ^ 32-1
- ULong: 一个无符号64位整数,范围从 0 到 2 ^ 64-1
1 | val b: UByte = 1u // UByte, expected type provided |
数值操作
整数除法
整数数字之间的除总是返回一个整数数字。任何小数部分都被丢弃。
1 | val x = 5 / 2 |
对于任意两种整数类型之间的除法都是如此:
1 | val x = 5L / 2 |
若要返回浮点类型,请显式地至少将其中一个参数转换为浮点类型:
1 | val x = 5 / 2.toDouble() |
位运算
Kotlin 提供了一系列函数用于二进制数的运算,这种运算又称为位运算。如果熟悉 Java 语言, 那你应该见过它们。
注:这里用了中缀的写法。
1 | // 整数转二进制 |
字符串
字符串在 Kotlin 是由类型字符串表示的。通常,字符串值是双引号(“)中的字符序列。
字符串是不可变的。初始化字符串后,不能更改其值或为其赋予新值。所有转换字符串的操作都将其结果返回到一个新的 String 对象中,而原始字符串保持不变:
1 | val str = "abcd" |
若要连接字符串,请使用 + 运算符。只要表达式中的第一个元素是字符串,就可以将字符串与其他类型的值连接起来:
1 | val s = "abc" + 1 |
截取
substring
substring 函数支持 IntRange 类型(表示一个整数范围的类型)的参数。这个 IntRange。
示例:
1 | val indexOfApostrophe = TAVERN_NAME.indexOf('\'') |
参数表示的范围决定要截取哪些位置的字符。这里,范围是从第一个字符直到撇号之前的字符 (until 创建的范围不包括上限值)。
split
split 函数返回的是 List 集合数据。使用给定的分隔符,该函数会返回一系列子字符串。
1 | val data = menuData.split(',') |
而 List 集合又支持一种叫解构(destructuring)的语法特性(这个 Kotlin 特性允许你在一个表达式里给多个变量赋值)。所以,如下所示, 我们可以用解构语法来替换一条条的赋值语句。
1 | val menuData = ... |
字符串操作
replace
replace 函数。顾名思义,此函数能按给定规则替换字符串中的 字符。replace 函数支持用正则表达式(稍后会展开讨论)确定要操作哪些字符,然后调用你定 义的匿名函数来确定如何替换匹配字符。
使用的 replace 函数需要两个值参。第一个是正则表达式(regex),用来决定要替换哪些字符。正则表达式可以针对你要搜索的字符定义通用的搜索模式。第二个值参是匿名函数,用来 确定该如何替换正则表达式搜索到的字符。
1 | phrase.replace(Regex("[aeiou]")) { |
字符串比较
使用结构相等操作符 == 来判断 name 变量值和 “Dragon’s Breath” 是否结 构相等。之前,你已见过用 == 判断数值相等的例子。用于字符串时,它会检查两个字符串中的字 符是否都匹配,顺序是否一致。
判断两个变量相等还有另一种方式:引用相等。这种方式会检查两个变量是否引用着同一个 类实例,也就是说两个变量是否指向内存堆上的同一对象。做引用相等判断时使用 === 符号。
引用相等比较很少用于比较字符串。一般来说,相比关心两个字符串是不是同一实例,我们更关心它们是不是相等字符,以及顺序是否一致(或者说两个不同的类实例是否结构相同)。
如果熟悉 Java 语言,那么你就知道,就比较字符串来说,== 操作符的行为方式和 Kotlin 不 一样,Java 用 == 做引用比较。要在 Java 中做结构相等比较,要用 equals 函数。
字符串遍历
String 类型还有一些其他函数,可以一次一个地遍历字符串,就像 indexOf 和 split 函数 一样。例如,使用 forEach 函数,你可以打印出小客栈饮料名字的每个字符,一次一个字符。
示例:
1 | "Dragon's Breath".forEach { println("$it") } |
许多这类函数也可以用于 List 类型。之后我们将见到大多数可以遍历集合的函数也适用于 String 类型。在很多方面,Kotlin 中 String 类型的行为就像字符集合。
raw 字符串
原始字符串可以包含换行符和任意文本。它由三重引号(“”")分隔,不包含转义,可以包含换行符和任何其他字符:
1 | val text = """ |
要从原始字符串中删除前导空格,请使用 trimMargin()
函数:
1 | val text = """ |
默认情况下,管道符号 | 用作边距前缀,但是您可以选择另一个字符并将其作为参数传递,如 trimMargin(">")
。
字符串模板
字符串模板允许您将变量引用和表达式包含到字符串中。当请求字符串的值时(例如,通过 println) ,所有引用和表达式都被实际值替换。
1 | val greeting = "Kotliner" |
- 打印具有变量引用的字符串。字符串中的引用以 $开头。
- 用表达式打印一个字符串。表达式以 $开头,用大括号括起来。
- 输出美元符。