Kotlin 全量关键字全面整理,并附上简洁示例,确保每一个关键字都清楚易懂。
Kotlin官方文档-基础知识-基础语法(翻译官方文档+自我总结)
Kotlin官方文档-基础知识-常用惯用语法(翻译官方文档+自我总结)
Kotlin官方文档-概念-类型-基本类型(翻译官方文档+自我总结)
在 Kotlin 中,一切皆对象,这意味着你可以对任何变量调用成员函数和属性。虽然某些类型在运行时会以原始值(例如数字、字符和布尔值)进行优化的内部表示,但它们对你而言看起来和行为都与普通类无异。
本节介绍 Kotlin 中使用的基本类型:
数值类型(Numbers)
编辑页面 2025年2月7日
整数类型(Integer types)
Kotlin 提供了一组内置类型用于表示数值。对于整数,有四种不同大小和值范围的类型:
| 类型(Type) | 大小(比特,Size (bits)) | 最小值(Min value) | 最大值(Max value) |
|---|---|---|---|
| Byte(字节) | 8 | -128 | 127 |
| Short(短整型) | 16 | -32768 | 32767 |
| Int(整型) | 32 | -2,147,483,648(-2³¹) | 2,147,483,647(2³¹ - 1) |
| Long(长整型) | 64 | -9,223,372,036,854,775,808(-2⁶³) | 9,223,372,036,854,775,807(2⁶³ - 1) |
除了有符号整数类型外,Kotlin 还提供了无符号整数类型。由于无符号整数针对的使用场景不同,会单独进行介绍。请参阅“无符号整数类型”部分。
当你初始化一个未显式指定类型的变量时,编译器会自动推断出能表示该值的最小范围类型,且从 Int 类型开始推断。如果值未超过 Int 类型的范围,则类型为 Int;如果超过了 Int 类型的范围,则类型为 Long。要显式指定一个 Long 类型的值,需在值后添加后缀 L。若要使用 Byte 或 Short 类型,需在声明时显式指定类型。显式指定类型后,编译器会检查该值是否未超过指定类型的范围。
val one = 1 // 推断为 Int 类型
val threeBillion = 3000000000 // 超过 Int 范围,推断为 Long 类型
val oneLong = 1L // 显式指定为 Long 类型
val oneByte: Byte = 1 // 显式指定为 Byte 类型
浮点类型(Floating-point types)
对于实数,Kotlin 提供了遵循 IEEE 754 标准的浮点类型 Float 和 Double。其中,Float 对应 IEEE 754 单精度浮点数,Double 对应双精度浮点数。
这两种类型的大小不同,可存储不同精度的浮点数:
| 类型(Type) | 大小(比特,Size (bits)) | 有效位数(Significant bits) | 指数位数(Exponent bits) | 十进制位数(Decimal digits) |
|---|---|---|---|---|
| Float | 32 | 24 | 8 | 6-7 |
| Double | 64 | 53 | 11 | 15-16 |
只有带小数部分的数值才能用于初始化 Double 和 Float 类型的变量。整数部分和小数部分用句点(.)分隔。
对于用小数初始化的变量,编译器会推断其类型为 Double:
val pi = 3.14 // 推断为 Double 类型
val one: Double = 1 // 此处会推断为 Int 类型,导致类型不匹配
// 错误提示:Initializer type mismatch(初始化器类型不匹配)
val oneDouble = 1.0 // 显式指定为 Double 类型
要显式指定一个值为 Float 类型,需在值后添加后缀 f 或 F。如果以此方式指定的数值包含超过 7 位的小数,则会进行四舍五入:
val e = 2.7182818284 // Double 类型
val eFloat = 2.7182818284f // Float 类型,实际值为 2.7182817
与某些其他语言不同,Kotlin 中的数值不存在隐式拓宽转换。例如,一个接收 Double 类型参数的函数,只能传入 Double 类型的值,而不能传入 Float、Int 或其他数值类型的值:
fun printDouble(x: Double) { print(x) }
val x = 1.0
val xInt = 1
val xFloat = 1.0f
printDouble(x) // 正确,传入 Double 类型值
printDouble(xInt) // 错误:Argument type mismatch(参数类型不匹配)
printDouble(xFloat) // 错误:Argument type mismatch(参数类型不匹配)
要将数值转换为其他类型,需使用显式转换。
数值的字面常量(Literal constants for numbers)
整数类型的字面常量有以下几种:
- 十进制:123
- 长整型,以大写 L 结尾:123L
- 十六进制:0x0F
- 二进制:0b00001011
Kotlin 不支持八进制字面常量。
Kotlin 也支持浮点数的常规表示法:
- Double 类型(当小数部分不以字母结尾时,默认为此类型):123.5、123.5e10
- Float 类型,以字母 f 或 F 结尾:123.5f
可以使用下划线来提高数值常量的可读性:
val oneMillion = 1_000_000 // 一百万,下划线分隔千位
val creditCardNumber = 1234_5678_9012_3456L // 信用卡号,按4位分组
val socialSecurityNumber = 999_99_9999L // 社保号,按格式分组
val hexBytes = 0xFF_EC_DE_5E // 十六进制字节,按字节分组
val bytes = 0b11010010_01101001_10010100_10010010 // 二进制字节,按字节分组
val bigFractional = 1_234_567.7182818284 // 带小数的大数值,千位分隔
无符号整数字面常量也有专门的后缀。有关无符号整数类型字面常量的更多信息,请参阅相关内容。
Java 虚拟机(JVM)上的数值装箱与缓存(Boxing and caching numbers on the Java Virtual Machine)
由于 JVM 对小数值(字节大小的数值)默认使用缓存机制,其存储数值的方式可能会导致代码出现不符合直觉的行为。
JVM 会将数值存储为基本数据类型,如 int、double 等。当使用泛型类型或创建可空数值引用(如 Int?)时,数值会被装箱到 Java 的包装类中,如 Integer 或 Double。
JVM 对表示 -128 到 127 之间数值的 Integer 及其他数值包装类采用了内存优化技术:所有指向此类对象的可空引用,都会指向同一个缓存对象。例如,以下代码中的可空对象在引用上是相等的:
val a: Int = 100
val boxedA: Int? = a
val anotherBoxedA: Int? = a
println(boxedA === anotherBoxedA) // 输出 true(引用相等)
对于超出此范围的数值,可空对象的引用并不相同,但结构上是相等的:
val b: Int = 10000
val boxedB: Int? = b
val anotherBoxedB: Int? = b
println(boxedB === anotherBoxedB) // 输出 false(引用不相等)
println(boxedB == anotherBoxedB) // 输出 true(结构相等)
因此,Kotlin 会对可装箱数值和字面常量使用引用相等性检查发出警告,提示信息为:“Identity equality for arguments of types ... and ... is prohibited.(禁止对...和...类型的参数使用引用相等性检查)”。比较 Int、Short、Long、Byte 类型(以及 Char 和 Boolean 类型)时,应使用结构相等性检查以获得一致的结果。
显式数值转换(Explicit number conversions)
由于不同数值类型的表示方式不同,它们之间不存在子类型关系。因此,小类型不会隐式转换为大类型,反之亦然。例如,将 Byte 类型的值赋值给 Int 类型的变量时,需要进行显式转换:
val byte: Byte = 1
// 正确,字面常量会进行静态检查
val intAssignedByte: Int = byte
// 错误:Initializer type mismatch(初始化器类型不匹配)
val intConvertedByte: Int = byte.toInt() // 显式转换为 Int 类型
println(intConvertedByte) // 输出 1
所有数值类型都支持转换为其他类型的方法:
- toByte():转换为 Byte 类型(Float 和 Double 类型的此方法已废弃)
- toShort():转换为 Short 类型
- toInt():转换为 Int 类型
- toLong():转换为 Long 类型
- toFloat():转换为 Float 类型
- toDouble():转换为 Double 类型
在很多情况下,不需要进行显式转换,因为编译器会根据上下文推断类型,并且算术运算符已重载以自动处理转换。例如:
val l = 1L + 3 // Long 类型 + Int 类型 => 结果为 Long 类型
println(l is Long) // 输出 true
不支持隐式转换的原因(Reasoning against implicit conversions)
Kotlin 不支持隐式转换,因为这可能导致意外行为。
如果不同类型的数值可以隐式转换,有时可能会悄无声息地丢失相等性和同一性。例如,假设 Int 是 Long 的子类型:
// 假设代码,实际无法编译:
val a: Int? = 1 // 装箱后的 Int 类型(对应 java.lang.Integer)
val b: Long? = a // 隐式转换为装箱后的 Long 类型(对应 java.lang.Long)
print(b == a) // 输出 "false",因为 Long.equals() 不仅检查值,还会检查另一方是否为 Long 类型
数值运算(Operations on numbers)
Kotlin 支持数值的标准算术运算:+(加)、-(减)、*(乘)、/(除)、%(取余)。这些运算被声明为相应类的成员函数:
println(1 + 2) // 输出 3
println(2_500_000_000L - 1L) // 输出 2499999999
println(3.14 * 2.71) // 输出约 8.5094
println(10.0 / 3) // 输出约 3.3333333333333335
你可以在自定义数值类中重写这些运算符。有关详细信息,请参阅“运算符重载”部分。
整数除法(Division of integers)
整数之间的除法运算结果始终是整数,任何小数部分都会被舍弃。
val x = 5 / 2
println(x == 2.5)
// 错误:Operator '==' cannot be applied to 'Int' and 'Double'(运算符 '==' 不能用于 'Int' 和 'Double' 类型)
println(x == 2)
// 输出 true(小数部分 0.5 被舍弃)
任何两种整数类型之间的除法运算都遵循此规则:
val x = 5L / 2
println(x == 2)
// 错误:Long 类型(x)不能与 Int 类型(2)比较
println(x == 2L)
// 输出 true(小数部分 0.5 被舍弃)
要使除法运算结果保留小数部分,需显式将其中一个运算数转换为浮点类型:
val x = 5 / 2.toDouble()
println(x == 2.5) // 输出 true
位运算(Bitwise operations)
Kotlin 提供了一组用于整数的位运算,这些运算直接对数值的二进制表示进行操作。位运算以函数形式呈现,可通过中缀表达式调用,且仅适用于 Int 和 Long 类型:
fun main() {
// 示例开始
val x = 1
val xShiftedLeft = (x shl 2) // 左移 2 位
println(xShiftedLeft)
// 输出 4(1 的二进制为 0001,左移 2 位后为 0100,即 4)
val xAnd = x and 0x000FF000 // 按位与运算
println(xAnd)
// 输出 0(1 的二进制与 000011111111000000000000 进行与运算,结果为 0)
// 示例结束
}
完整的位运算列表如下:
- shl(bits) – 有符号左移
- shr(bits) – 有符号右移
- ushr(bits) – 无符号右移
- and(bits) – 按位与
- or(bits) – 按位或
- xor(bits) – 按位异或
- inv() – 按位取反
浮点数比较(Floating-point numbers comparison)
本节讨论的浮点数运算包括:
- 相等性检查:a == b 和 a != b
- 比较运算符:a < b、a > b、a <= b、a >= b
- 范围创建和范围检查:a..b(创建范围)、x in a..b(x 在范围内)、x !in a..b(x 不在范围内)
当操作数 a 和 b 静态已知为 Float、Double 类型或其可空对应类型(类型已声明、已推断或为智能类型转换的结果)时,数值运算及其创建的范围会遵循 IEEE 754 浮点数算术标准。
然而,为了支持泛型使用场景并提供全序关系,当操作数未静态类型化为浮点数时(例如 Any、Comparable<...> 或 Collection 类型),行为会有所不同。在这种情况下,运算会使用 Float 和 Double 类型的 equals 和 compareTo 实现。因此会出现以下情况:
- NaN 被认为与自身相等
- NaN 被认为大于任何其他元素,包括 POSITIVE_INFINITY(正无穷)
- 0.0 被认为小于 0.0
以下示例展示了静态类型化为浮点数的操作数(Double.NaN)与非静态类型化为浮点数的操作数(listOf(T))之间的行为差异:
// 操作数静态类型化为浮点数
println(Double.NaN == Double.NaN) // 输出 false(遵循 IEEE 754 标准)
// 操作数非静态类型化为浮点数
// 因此 NaN 被认为与自身相等
println(listOf(Double.NaN) == listOf(Double.NaN)) // 输出 true
// 操作数静态类型化为浮点数
println(0.0 == -0.0) // 输出 true(遵循 IEEE 754 标准)
// 操作数非静态类型化为浮点数
// 因此 -0.0 被认为小于 0.0
println(listOf(0.0) == listOf(-0.0)) // 输出 false
// 非静态类型化浮点数的排序
println(listOf(Double.NaN, Double.POSITIVE_INFINITY, 0.0, -0.0).sorted())
// 输出 [-0.0, 0.0, Infinity, NaN](NaN 排在最后)
无符号整数类型(Unsigned integer types)
编辑页面 2025年2月7日
除了有符号整数类型外,Kotlin 还提供了以下用于表示无符号整数的类型:
| 类型(Type) | 大小(比特,Size (bits)) | 最小值(Min value) | 最大值(Max value) |
|---|---|---|---|
| UByte(无符号字节) | 8 | 0 | 255 |
| UShort(无符号短整型) | 16 | 0 | 65,535 |
| UInt(无符号整型) | 32 | 0 | 4,294,967,295(2³² - 1) |
| ULong(无符号长整型) | 64 | 0 | 18,446,744,073,709,551,615(2⁶⁴ - 1) |
无符号类型支持其对应有符号类型的大部分运算。
无符号整数以内联类(inline classes)的形式实现,这类内联类包含一个单独的存储属性,该属性的类型为对应宽度的有符号整数类型。如果需要在无符号整数类型和有符号整数类型之间进行转换,请确保更新代码,使所有函数调用和运算都支持新的类型。
无符号数组和范围(Unsigned arrays and ranges)
无符号数组及其相关运算目前处于 Beta 测试阶段,随时可能发生不兼容的变更。使用前需要主动启用(详见下文说明)。
与基本数据类型类似,每种无符号类型都有对应的数组类型:
- UByteArray:无符号字节数组
- UShortArray:无符号短整型数组
- UIntArray:无符号整型数组
- ULongArray:无符号长整型数组
与有符号整数数组类似,这些无符号数组提供了与 Array 类相似的 API,同时避免了装箱操作带来的开销。
使用无符号数组时,会收到一条警告,提示该特性尚未稳定。要移除该警告,需通过 @ExperimentalUnsignedTypes 注解主动启用该特性。你可以自行决定是否要求调用者显式启用对你的 API 的使用,但请记住,无符号数组并非稳定特性,因此使用该特性的 API 可能会因语言的变更而失效。有关主动启用要求的更多信息,请参阅相关文档。
UInt 和 ULong 类型支持范围(Ranges)和递进(Progressions)功能,对应的实现类为 UIntRange、UIntProgression、ULongRange 和 ULongProgression。这些类与无符号整数类型一样,均已处于稳定状态。
无符号整数字面常量(Unsigned integers literals)
为了便于使用无符号整数,可以在整数字面常量后添加后缀,以指定具体的无符号类型(类似 Float 类型的后缀 F 或 Long 类型的后缀 L):
后缀 u 或 U 表示无符号字面常量,但不指定具体的类型。如果未提供预期类型,编译器会根据字面常量的大小,将其推断为 UInt 或 ULong 类型:
val b: UByte = 1u // UByte 类型,已提供预期类型
val s: UShort = 1u // UShort 类型,已提供预期类型
val l: ULong = 1u // ULong 类型,已提供预期类型
val a1 = 42u // UInt 类型:未提供预期类型,常量大小适合 UInt 范围
val a2 = 0xFFFF_FFFF_FFFFu // ULong 类型:未提供预期类型,常量大小超出 UInt 范围
后缀 uL 或 UL 显式指定字面常量为无符号长整型(ULong):
val a = 1UL // ULong 类型,即使未提供预期类型且常量大小适合 UInt 范围
使用场景(Use cases)
无符号整数的主要使用场景是利用整数的完整位范围来表示正值。
例如,用于表示超出有符号类型范围的十六进制常量,如 32 位 AARRGGBB 格式的颜色值:
data class Color(val representation: UInt)
val yellow = Color(0xFFCC00CCu)
使用无符号整数初始化字节数组时,无需对字面常量显式调用 toByte() 进行转换:
val byteOrderMarkUtf8 = ubyteArrayOf(0xEFu, 0xBBu, 0xBFu)
另一个使用场景是与原生 API 进行互操作。Kotlin 允许在签名中表示包含无符号类型的原生声明。这种映射不会将无符号整数替换为有符号整数,从而保持语义不变。
非目标场景(Non-goals)
尽管无符号整数只能表示正数和零,但它的设计目标并非用于应用领域中需要表示非负整数的场景。例如,不能将其用作集合大小或集合索引值的类型。
原因如下:
- 使用有符号整数有助于检测意外的溢出,并发出错误提示,例如空列表的 List.lastIndex 为 -1 这种错误场景。
- 无符号整数不能被视为有符号整数的范围受限版本,因为它们的值范围并非有符号整数范围的子集。有符号整数和无符号整数彼此都不是对方的子类型。
Kotlin 中无符号整数(Unsigned) 和有符号整数(Signed) 的核心区别(笔记+总结)
1.一句话总结
- 有符号整数:「能存负数,但正数范围减半」,适合需要表示正负的通用场景;
- 无符号整数:「不能存负数,但正数范围翻倍」,适合需要最大化利用位宽存储非负数据的特殊场景。
2. 字面常量声明不同
- 有符号整数:直接写数值(如
100、200、3000000000L); - 无符号整数:需加后缀
u/U(如100u、4294967295u),uL/UL显式指定 ULong(如1UL)。
3. 运算与转换规则不同
- 运算逻辑:无符号整数的「溢出」行为不同(有符号溢出会出现负数,无符号溢出会循环到 0);
- 类型转换:两者需显式转换(如
intValue.toUInt()、uintValue.toInt()),无隐式转换(彼此不是对方子类型)。
4. 适用场景完全分离
- 有符号整数:日常开发绝大多数场景(如计数、金额、温度、数组索引偏移);
- 无符号整数:仅特定场景(如 32 位 AARRGGBB 颜色值
0xFFCC00CCu、字节数组ubyteArrayOf(0xEFu)、原生 C/C++ API 互操作)。
布尔类型(Booleans)
编辑页面 2025年2月10日
Boolean 类型用于表示布尔值对象,它只有两个可能的值:true(真)和 false(假)。Boolean 类型有一个可空对应类型,声明为 Boolean?。
在 JVM 平台上,以基本数据类型 boolean 存储的布尔值通常占用 8 个比特(bits)。
布尔类型支持的内置运算包括:
- || —— 析取运算(逻辑或)
- && —— 合取运算(逻辑与)
- ! —— 否定运算(逻辑非)
示例:
val myTrue: Boolean = true
val myFalse: Boolean = false
val boolNull: Boolean? = null
println(myTrue || myFalse)
// 输出 true
println(myTrue && myFalse)
// 输出 false
println(!myTrue)
// 输出 false
println(boolNull)
// 输出 null
|| 和 && 运算符采用惰性求值(短路求值)方式,这意味着:
- 对于 || 运算符,如果第一个操作数为 true,则不会计算第二个操作数。
- 对于 && 运算符,如果第一个操作数为 false,则不会计算第二个操作数。
在 JVM 平台上,与数值类型类似,布尔值对象的可空引用会被装箱到 Java 的包装类中。
字符类型(Characters)
编辑页面 2025年2月10日
字符由 Char 类型表示。字符字面量用单引号包裹:'1'。
在 JVM 平台上,以基本数据类型 char 存储的字符,表示一个 16 位的 Unicode 字符。
特殊字符以转义反斜杠 \ 开头。支持以下转义序列:
- \t —— 制表符(Tab)
- \b —— 退格符(Backspace)
- \n —— 换行符(LF,Line Feed)
- \r —— 回车符(CR,Carriage Return)
- \’ —— 单引号
- \” —— 双引号
- \ —— 反斜杠
- $ —— 美元符号
要表示其他任意字符,可使用 Unicode 转义序列语法:'\uFF00'。
val aChar: Char = 'a'
println(aChar)
println('\n') // 输出一个额外的换行符
println('\uFF00')
如果字符变量的值是一个数字字符,可以使用 digitToInt() 函数将其显式转换为 Int 类型的数值。
在 JVM 平台上,与数值类型类似,当需要可空引用时,字符会被装箱到 Java 的包装类中。装箱操作不会保留引用同一性。
编辑页面 2025年6月26日
Kotlin 中的字符串由 String 类型表示。
在 JVM 上,UTF-16 编码的 String 类型对象每个字符大约占用 2 个字节。
通常,字符串值是用双引号(")括起来的字符序列:
val str = "abcd 123"
字符串的元素是字符,可通过索引操作 s[i] 访问。也可以使用 for 循环遍历这些字符:
for (c in str) {
println(c)
}
字符串是不可变的。一旦初始化字符串,就无法修改其值或为其分配新值。所有对字符串进行转换的操作都会在新的 String 对象中返回结果,而原始字符串保持不变:
val str = "abcd"
// 创建并打印一个新的 String 对象
println(str.uppercase())
// 输出:ABCD
// 原始字符串保持不变
println(str)
// 输出:abcd
要连接字符串,可以使用 + 运算符。该运算符也可用于连接字符串与其他类型的值,只要表达式中的第一个元素是字符串即可:
val s = "abc" + 1
println(s + "def")
// 输出:abc1def
在大多数情况下,使用字符串模板或多行字符串比字符串连接更合适。
字符串字面量(String literals)
Kotlin 有两种字符串字面量:
- 转义字符串(Escaped strings)
- 多行字符串(Multiline strings)
转义字符串(Escaped strings)
转义字符串可以包含转义字符。
以下是转义字符串的示例:
val s = "Hello, world!\n"
转义采用常规方式,使用反斜杠(\)。
有关支持的转义序列列表,请参阅《字符(Characters)》页面。
多行字符串(Multiline strings)
多行字符串可以包含换行符和任意文本。它用三重引号(""")界定,不包含转义字符,可包含换行符和其他任何字符:
val text = """
for (c in "foo")
print(c)
"""
要移除多行字符串中的前导空格,可以使用 trimMargin() 函数:
val text = """
|Tell me and I forget.
|Teach me and I remember.
|Involve me and I learn.
|(Benjamin Franklin)
""".trimMargin()
默认情况下,竖线符号 | 用作边距前缀,也可以选择其他字符并将其作为参数传入,例如 trimMargin(">")。
字符串模板(String templates)
字符串字面量可以包含模板表达式——即会被求值并将结果拼接到字符串中的代码片段。处理模板表达式时,Kotlin 会自动调用表达式结果的 .toString() 函数,将其转换为字符串。模板表达式以美元符号($)开头,有两种形式:
一种是直接后跟变量名:
val i = 10
println("i = $i")
// 输出:i = 10
val letters = listOf("a","b","c","d","e")
println("Letters: $letters")
// 输出:Letters: [a, b, c, d, e]
另一种是用大括号括起来的表达式:
val s = "abc"
println("$s.length is ${s.length}")
// 输出:abc.length is 3
模板可用于转义字符串和多行字符串。但多行字符串不支持反斜杠转义。要在多行字符串中,在标识符开头允许的符号前插入美元符号 $,需使用以下语法:
val price = """
${'$'}_9.99
"""
若要避免在字符串中出现 {''} 序列,可以使用实验性的“多美元符号字符串插值”特性。
多美元符号字符串插值(Multi-dollar string interpolation)
多美元符号字符串插值允许指定需要连续多少个美元符号才能触发插值。插值是指将变量或表达式直接嵌入字符串的过程。
虽然可以对单行字符串中的字面量进行转义,但 Kotlin 中的多行字符串不支持反斜杠转义。要将美元符号({'$'} 结构来防止字符串插值。这种方式可能会降低代码的可读性,尤其是当字符串中包含多个美元符号时。
多美元符号字符串插值简化了这一操作,它允许在单行和多行字符串中将美元符号视为字面字符。例如:
val KClass<*>.jsonSchema : String
get() = $$"""
{
"$schema": "<https://json-schema.org/draft/2020-12/schema>",
"$id": "<https://example.com/product.schema.json>",
"$dynamicAnchor": "meta",
"title": "$${simpleName ?: qualifiedName ?: "unknown"}",
"type": "object"
}
"""
在此示例中,$$ 前缀指定需要两个连续的美元符号才能触发字符串插值。单个美元符号将保留为字面字符。
可以调整触发插值所需的美元符号数量。例如,使用三个连续的美元符号()时,$ 和 $$ 将保留为字面字符,而 可用于触发插值:
val productName = "carrot"
val requestedData =
$$$"""{
"currency": "$",
"enteredAmount": "42.45 $$",
"$$serviceField": "none",
"product": "$$$productName"
}
"""
println(requestedData)
// 输出:
//{
// "currency": "$",
// "enteredAmount": "42.45 $$",
// "$$serviceField": "none",
// "product": "carrot"
//}
在此示例中,$$$ 前缀允许字符串包含 和 $$,而无需使用{'$'} 结构进行转义。
多美元符号字符串插值不会影响使用单美元符号字符串插值的现有代码。可以继续像以前一样使用单个 $,并在需要处理字符串中的字面美元符号时使用多美元符号。
字符串格式化(String formatting)
只有 Kotlin/JVM 支持 String.format() 函数进行字符串格式化。
要根据特定需求格式化字符串,可以使用 String.format() 函数。
String.format() 函数接受一个格式字符串和一个或多个参数。格式字符串中包含每个参数对应的占位符(用 % 表示),占位符后紧跟格式说明符。格式说明符是对相应参数的格式化指令,由标志、宽度、精度和转换类型组成。这些格式说明符共同决定了输出的格式。常见的格式说明符包括:用于整数的 %d、用于浮点数的 %f 以及用于字符串的 %s。也可以使用 argument_index$ 语法在格式字符串中多次以不同格式引用同一个参数。
有关格式说明符的详细说明和完整列表,请参阅 Java 的 Class Formatter 文档。
以下是示例:
// 格式化整数,补前导零使其长度达到 7 个字符
val integerNumber = String.format("%07d", 31416)
println(integerNumber)
// 输出:0031416
// 格式化浮点数,显示正号并保留四位小数
val floatNumber = String.format("%+.4f", 3.141592)
println(floatNumber)
// 输出:+3.1416
// 格式化两个字符串为大写,每个字符串对应一个占位符
val helloString = String.format("%S %S", "hello", "world")
println(helloString)
// 输出:HELLO WORLD
// 格式化负数,用括号括起来,然后使用 argument_index$ 以另一种格式(不带括号)重复该数字
val negativeNumberInParentheses = String.format("%(d means %1\$d", -31416)
println(negativeNumberInParentheses)
// 输出:(31416) means -31416
String.format() 函数提供的功能与字符串模板类似,但它更灵活,因为它支持更多的格式化选项。
此外,还可以通过变量指定格式字符串。这在格式字符串需要动态变化的场景中非常有用,例如根据用户区域设置进行本地化时。
使用 String.format() 函数时需注意,参数的数量或位置容易与对应的占位符不匹配。
数组(Arrays)
编辑页面 2024年9月25日
数组是一种数据结构,用于存储固定数量的相同类型或其亚型的值。Kotlin 中最常见的数组类型是对象类型数组,由 Array 类表示。
如果在对象类型数组中使用基本数据类型,会对性能产生影响,因为基本数据类型会被装箱为对象。为避免装箱带来的额外开销,应改用基本数据类型数组。
何时使用数组(When to use arrays)
当需要满足特定的底层需求时,可在 Kotlin 中使用数组。例如,当性能要求超出常规应用所需,或需要构建自定义数据结构时。若没有此类限制,建议使用集合(collections)。
与数组相比,集合具有以下优势:
集合可以是只读的,这能提供更强的控制权,让你编写意图清晰且健壮的代码。
向集合中添加或删除元素非常简便。相比之下,数组的大小是固定的。要向数组中添加或删除元素,唯一的方法是每次都创建一个新数组,这效率非常低:
var riversArray = arrayOf("Nile", "Amazon", "Yangtze")
// 使用 += 赋值操作会创建一个新的 riversArray,
// 复制原始元素并添加 "Mississippi"
riversArray += "Mississippi"
println(riversArray.joinToString())
// 输出:Nile, Amazon, Yangtze, Mississippi
可以使用相等运算符(==)检查集合是否在结构上相等。但该运算符不能用于数组,必须使用专门的函数,有关详情请参阅“比较数组”部分。
有关集合的更多信息,请参阅《集合概述》(Collections overview)。
创建数组(Create arrays)
在 Kotlin 中,可通过以下方式创建数组:
- 使用函数,如
arrayOf()、arrayOfNulls()或emptyArray()。 - 使用
Array构造函数。
以下示例使用 arrayOf() 函数,并向其传入元素值:
// 创建一个包含值 [1, 2, 3] 的数组
val simpleArray = arrayOf(1, 2, 3)
println(simpleArray.joinToString())
// 输出:1, 2, 3
```Kotlin
以下示例使用 `arrayOfNulls()` 函数创建一个指定大小、元素均为 `null` 的数组:
// 创建一个包含值 [null, null, null] 的数组 val nullArray: Array<Int?> = arrayOfNulls(3) println(nullArray.joinToString()) // 输出:null, null, null
以下示例使用 `emptyArray()` 函数创建一个空数组:
```Kotlin
var exampleArray = emptyArray<String>()
借助 Kotlin 的类型推断功能,可在赋值运算符的左侧或右侧指定空数组的类型。
例如:
var exampleArray = emptyArray<String>()
var exampleArray: Array<String> = emptyArray()
Array 构造函数接收两个参数:数组大小和一个函数——该函数会根据索引返回数组元素的值:
// 创建一个初始值均为 0 的 Array<Int>,值为 [0, 0, 0]
val initArray = Array<Int>(3) { 0 }
println(initArray.joinToString())
// 输出:0, 0, 0
// 创建一个 Array<String>,值为 ["0", "1", "4", "9", "16"]
val asc = Array(5) { i -> (i * i).toString() }
asc.forEach { print(it) }
// 输出:014916
与大多数编程语言一样,Kotlin 中的数组索引从 0 开始。
嵌套数组(Nested arrays)
数组可以嵌套,从而创建多维数组:
// 创建一个二维数组
val twoDArray = Array(2) { Array<Int>(2) { 0 } }
println(twoDArray.contentDeepToString())
// 输出:[[0, 0], [0, 0]]
// 创建一个三维数组
val threeDArray = Array(3) { Array(3) { Array<Int>(3) { 0 } } }
println(threeDArray.contentDeepToString())
// 输出:[[[0, 0, 0], [0, 0, 0], [0, 0, 0]], [[0, 0, 0], [0, 0, 0], [0, 0, 0]], [[0, 0, 0], [0, 0, 0], [0, 0, 0]]]
嵌套数组的类型和大小可以不同。
访问和修改元素(Access and modify elements)
数组始终是可变的。要访问和修改数组中的元素,可使用索引访问运算符 []:
val simpleArray = arrayOf(1, 2, 3)
val twoDArray = Array(2) { Array<Int>(2) { 0 } }
// 访问并修改元素
simpleArray[0] = 10
twoDArray[0][0] = 2
// 打印修改后的元素
println(simpleArray[0].toString()) // 输出:10
println(twoDArray[0][0].toString()) // 输出:2
Kotlin 中的数组是协变的(invariant)。这意味着 Kotlin 不允许将 Array<String> 赋值给 Array<Any>,以防止可能出现的运行时错误。若需实现类似功能,可使用 Array<out Any>。有关更多信息,请参阅《类型投影》(Type Projections)。
操作数组(Work with arrays)
在 Kotlin 中,可通过以下方式操作数组:将数组用于向函数传递可变数量的参数,或对数组本身执行操作(如比较数组、转换数组内容、将数组转换为集合等)。
向函数传递可变数量的参数(Pass variable number of arguments to a function)
在 Kotlin 中,可通过 vararg 参数向函数传递可变数量的参数。当提前不知道参数数量时(如格式化消息或创建 SQL 查询时),这非常有用。
要将包含可变数量参数的数组传递给函数,需使用展开运算符(*)。展开运算符会将数组的每个元素作为单独的参数传递给指定函数:
fun main() {
val lettersArray = arrayOf("c", "d")
printAllStrings("a", "b", *lettersArray)
// 输出:abcd
}
fun printAllStrings(vararg strings: String) {
for (string in strings) {
print(string)
}
}
有关更多信息,请参阅《可变数量的参数(varargs)》(Variable number of arguments (varargs))。
比较数组(Compare arrays)
要比较两个数组是否包含相同顺序的相同元素,需使用 .contentEquals() 和 .contentDeepEquals() 函数:
val simpleArray = arrayOf(1, 2, 3)
val anotherArray = arrayOf(1, 2, 3)
// 比较数组内容
println(simpleArray.contentEquals(anotherArray))
// 输出:true
// 修改一个元素后,使用中缀表示法比较数组内容
simpleArray[0] = 10
println(simpleArray contentEquals anotherArray)
// 输出:false
不要使用相等运算符(==)和不等运算符(!=)比较数组内容。这些运算符用于检查被赋值的变量是否指向同一个对象。
要了解 Kotlin 中数组为何会有这种行为,请阅读我们的博客文章。
转换数组(Transform arrays)
Kotlin 提供了许多用于转换数组的实用函数。本文仅重点介绍其中几个,并非全部。完整的函数列表请参阅我们的 API 参考文档。
求和(Sum)
要计算数组中所有元素的和,使用 .sum() 函数:
val sumArray = arrayOf(1, 2, 3)
// 计算数组元素的和
println(sumArray.sum())
// 输出:6
.sum() 函数仅可用于数值类型的数组(如 Int 数组)。
打乱(Shuffle)
要随机打乱数组中的元素,使用 .shuffle() 函数:
val simpleArray = arrayOf(1, 2, 3)
// 打乱元素,结果可能为 [3, 2, 1]
simpleArray.shuffle()
println(simpleArray.joinToString())
// 再次打乱元素,结果可能为 [2, 3, 1]
simpleArray.shuffle()
println(simpleArray.joinToString())
将数组转换为集合(Convert arrays to collections)
如果需要对接不同的 API(有些使用数组,有些使用集合),可以将数组转换为集合,反之亦然。
转换为 List 或 Set(Convert to List or Set)
要将数组转换为 List 或 Set,使用 .toList() 和 .toSet() 函数:
val simpleArray = arrayOf("a", "b", "c", "c")
// 转换为 Set
println(simpleArray.toSet())
// 输出:[a, b, c]
// 转换为 List
println(simpleArray.toList())
// 输出:[a, b, c, c]
转换为 Map(Convert to Map)
要将数组转换为 Map,使用 .toMap() 函数。
只有 Pair<K,V> 类型的数组才能转换为 Map。Pair 实例的第一个值会成为键(key),第二个值会成为值(value)。以下示例使用中缀表示法调用 to 函数,创建 Pair 元组:
val pairArray = arrayOf("apple" to 120, "banana" to 150, "cherry" to 90, "apple" to 140)
// 转换为 Map
// 键为水果名称,值为其卡路里含量
// 注意:键必须唯一,因此 "apple" 的最新值会覆盖第一个值
println(pairArray.toMap())
// 输出:{apple=140, banana=150, cherry=90}
基本数据类型数组(Primitive-type arrays)
如果使用 Array 类存储基本数据类型的值,这些值会被装箱为对象。作为替代方案,可以使用基本数据类型数组,它能在数组中存储基本数据类型,且不会产生装箱开销:
| Kotlin 基本数据类型数组 | 对应的 Java 数组 |
|---|---|
| BooleanArray | boolean[] |
| ByteArray | byte[] |
| CharArray | char[] |
| DoubleArray | double[] |
| FloatArray | float[] |
| IntArray | int[] |
| LongArray | long[] |
| ShortArray | short[] |
这些类与 Array 类没有继承关系,但拥有相同的函数和属性集。
以下示例创建 IntArray 类的实例:
// 创建一个大小为 5、初始值均为 0 的 Int 数组
val exampleArray = IntArray(5)
println(exampleArray.joinToString())
// 输出:0, 0, 0, 0, 0
💡
要将基本数据类型数组转换为对象类型数组,使用 .toTypedArray() 函数。
要将对象类型数组转换为基本数据类型数组,使用 .toBooleanArray()、.toByteArray()、.toCharArray() 等函数。