数字、布尔值与字符¶
Kotlin 的基础类型在源码层面看起来都是对象:Int、Long、Boolean、Char 都可以调用成员函数。但这不等于它们在 JVM 上总是对象。Kotlin 编译器会在很多场景下把非空基础值编译成 JVM primitive,以保留性能;在可空、泛型、反射或互操作场景中才可能装箱。
本章覆盖官方 Numbers、Booleans、Characters 和 Unsigned integer types 页面,并补充 Java 迁移时最容易踩的点。
数字类型总览¶
Kotlin 数字分为整数和浮点数:
| 类型 | 位数 | Java 接近类型 | 常见用途 |
|---|---|---|---|
Byte |
8 | byte / Byte |
二进制协议、文件格式 |
Short |
16 | short / Short |
特定协议或旧 API |
Int |
32 | int / Integer |
默认整数选择 |
Long |
64 | long / Long |
大范围整数、时间戳、ID |
Float |
32 | float / Float |
低精度浮点、图形/模型 API |
Double |
64 | double / Double |
默认小数选择 |
官方建议可以简化成:
- 整数默认用
Int。 - 超过
Int范围用Long。 - 小数默认用
Double。 - 只有 API、数据格式或性能明确要求时才用
Float、Byte、Short。
Java 对比:Java 里 int 和 Integer 是两套类型;Kotlin 源码通常只写 Int,编译器根据上下文决定是否使用 primitive 或 boxed representation。
整数字面量¶
Kotlin 支持这些整数字面量:
val decimal = 123
val hex = 0x0F
val binary = 0b00001011
不支持八进制字面量。Java 里历史上 0123 是八进制,这在 Kotlin 中不会成立,避免了不少误读。
可以使用下划线提高可读性:
val oneMillion = 1_000_000
val cardNumber = 1234_5678_9012_3456L
val bytes = 0b01010010_01101001_10010100_10010010
Long 字面量可加 L:
val id = 42L
不写类型时:
- 如果值适合
Int,推断为Int。 - 如果值超出
Int但适合Long,推断为Long。
val million = 1_000_000 // Int
val threeBillion = 3_000_000_000 // Long
显式类型会让编译器检查范围:
val oneByte: Byte = 1
// val tooBig: Byte = 128 // 编译错误
浮点数字面量¶
小数字面量默认是 Double:
val pi = 3.14 // Double
val distance = 1e3 // Double,1000.0
要声明 Float,使用 f 或 F 后缀:
val ratio = 0.75f
Float 只有约 6 到 7 位十进制有效数字,Double 约 15 到 16 位。金融金额、精确十进制、税费计算不要用二进制浮点数,通常应该使用 BigDecimal 或整数分单位建模。
Java 对比:这点和 Java 一样,float / double 都遵循 IEEE 754。Kotlin 没有改变浮点数的底层语义。
整数除法¶
两个整数相除,结果仍是整数:
val a = 5 / 2
println(a) // 2
要得到小数,至少一个操作数要是 Float 或 Double:
val b = 5 / 2.0
println(b) // 2.5
val c = 5.toDouble() / 2
println(c) // 2.5
Java 对比:这和 Java 一致。迁移时不要因为 Kotlin 语法更高级就误以为 / 会自动返回小数。
没有隐式数字转换¶
Kotlin 不允许把 Int 隐式当作 Long 或 Double 传参:
fun printDouble(value: Double) {
println(value)
}
val intValue = 1
// printDouble(intValue) // 编译错误
printDouble(intValue.toDouble())
也不能把 Int 直接赋值给 Long:
val i = 1
// val l: Long = i // 编译错误
val l: Long = i.toLong()
Java 对比:Java 允许很多 widening primitive conversion,例如 int 到 long、double。Kotlin 更严格,要求显式转换,目的是避免 API 调用中悄悄发生精度或语义变化。
所有数字类型都支持显式转换:
val value = 42
value.toByte()
value.toShort()
value.toInt()
value.toLong()
value.toFloat()
value.toDouble()
浮点转整数会丢弃小数部分:
val d = 1.9
println(d.toInt()) // 1
这不是四舍五入。需要四舍五入时使用 roundToInt() 等函数。
混合数字表达式¶
Kotlin 不支持函数参数和赋值中的隐式转换,但算术表达式可以混合不同数字类型,结果类型由操作数决定:
val intNumber: Int = 1
val longNumber: Long = 1000
val result = intNumber + longNumber // Long
不能把结果塞进更小类型:
// val wrong: Int = intNumber + longNumber // 编译错误
建议公共 API 中避免让调用者猜结果类型。需要明确语义时,先转换再计算:
val total: Long = intNumber.toLong() + longNumber
溢出¶
整数运算溢出不会自动报错:
val max = Int.MAX_VALUE
println(max + 1) // -2147483648
负数取反也可能溢出:
val min = Int.MIN_VALUE
println(-min) // -2147483648
原因是 Int.MIN_VALUE 的绝对值超出 Int.MAX_VALUE。
窄化转换也可能改变值:
val large = 130
val narrowed: Byte = large.toByte()
println(narrowed) // -126
实践建议:
- 计数、金额、容量等接近上限的值优先评估
Long。 - 安全敏感、金融、库存等场景不要依赖溢出回绕。
- Java 有
Math.addExact()等精确检查工具,Kotlin/JVM 可以直接使用。
位运算¶
Kotlin 没有 Java 风格的 <<、>>、>>>、&、|、^ 数字位运算符。它提供 Int 和 Long 上的中缀函数:
val x = 1
println(x shl 2) // 4
println(x and 0xFF) // 1
println(x or 0x10) // 17
println(x xor 0x03) // 2
println(x.inv()) // 按位取反
常用函数:
shl():有符号左移。shr():有符号右移。ushr():无符号右移。and():按位与。or():按位或。xor():按位异或。inv():按位取反。
Java 对比:
int result = (x << 2) & 0xFF;
Kotlin:
val result = (x shl 2) and 0xFF
这能避免位运算符和布尔运算符混用,但对底层开发者需要适应。
浮点比较的特殊规则¶
当操作数静态类型是 Float 或 Double 时,比较遵循 IEEE 754:
println(Double.NaN == Double.NaN) // false
println(0.0 == -0.0) // true
但当值通过非浮点静态类型参与比较,例如 Any、泛型、集合排序,行为会使用 equals() / compareTo() 实现:
fun equalsAsAny(a: Any, b: Any): Boolean = a == b
println(equalsAsAny(Double.NaN, Double.NaN)) // true
println(equalsAsAny(0.0, -0.0)) // false
官方文档特别指出,在泛型场景中:
NaN被认为等于自身。NaN被认为大于任何其他元素,包括正无穷。-0.0被认为小于0.0。
实践建议:
- 不要用浮点数做精确业务等值判断。
- 排序、去重、Map key 中使用浮点数要格外小心。
- 如果领域允许误差,写明确的 epsilon 比较函数。
JVM 装箱与数字缓存¶
Kotlin/JVM 中,非空数字通常编译为 primitive:
val count: Int = 10
但以下场景可能装箱:
- 可空数字:
Int?。 - 泛型:
List<Int>中的元素。 Any。- Java 互操作需要对象类型。
JVM 会缓存一部分小整数的 boxed 对象。以 Integer 为例,通常缓存 -128 到 127:
val score: Int = 100
val a: Int? = score
val b: Int? = score
println(a === b) // true,可能来自 JVM 缓存
超出缓存范围:
val score: Int = 10_000
val a: Int? = score
val b: Int? = score
println(a === b) // false
println(a == b) // true
结论:数字值比较永远用 ==,不要用 ===。=== 比较引用身份,只在你明确需要对象身份时使用。
Java 对比:Java 中 Integer a = 100; Integer b = 100; a == b 也可能因为缓存返回 true,而大整数返回 false。Kotlin 的 == 会调用结构相等,规避了大部分误用。
Boolean¶
Boolean 只有两个值:true 和 false。
val enabled: Boolean = true
val visible = false
val optional: Boolean? = null
Kotlin 不允许把整数当布尔值:
// val enabled: Boolean = 1 // 编译错误
Java 也不允许 int 直接作为 boolean,但 C/C++、JavaScript 等语言允许类似写法。Kotlin 保持严格。
Boolean 运算¶
逻辑非:
val isOn = true
val isOff = !isOn
逻辑与和或:
val canAccess = isLoggedIn && hasPermission
val shouldShow = isAdmin || isOwner
&& 和 || 会短路:
fun expensiveCheck(): Boolean {
println("checked")
return true
}
val result = false && expensiveCheck() // expensiveCheck 不会执行
如果你确实要两边都执行,可以用 and / or 中缀函数:
val result = false and expensiveCheck() // expensiveCheck 会执行
异或使用 xor:
val onlyOne = a xor b
优先级上,官方文档给出的顺序是:
!xor等中缀函数&&||
实践建议:复杂布尔表达式主动加括号,不要让读者背优先级。
Boolean? 不是三值逻辑的万能解¶
Boolean? 可以表达 true、false、null,但不要随意用它表示业务状态:
val approved: Boolean? = null
这可能表示“未审核”“未知”“不适用”“加载失败”。更清晰的做法是 sealed class 或 enum:
enum class ApprovalStatus {
Pending,
Approved,
Rejected,
}
只有当 null 的含义非常明确,例如“配置未提供”,才使用 Boolean?。
Char¶
Char 表示一个 UTF-16 code unit:
val letter: Char = 'a'
val digit = '1'
val symbol = '!'
val space = ' '
字符字面量必须恰好包含一个字符:
// val invalid = 'AB' // 编译错误
// val empty = '' // 编译错误
Char 不是数字类型:
val c = 'A'
// val code: Int = c // 编译错误
val code: Int = c.code
Java 对比:Java char 本质上是 16 位无符号整数,很多时候可以参与数字运算。Kotlin 明确把 Char 与数字类型分开,需要显式 .code 或转换函数。
Unicode 与 surrogate pair¶
官方文档强调:Kotlin Char 存储的是 UTF-16 code unit,不一定是完整 Unicode 字符。
BMP 范围内字符可以用一个 Char 表示:
val one = '\u0031'
println(one) // 1
BMP 之外的字符,例如很多 emoji,需要两个 Char 组成 surrogate pair:
val emoji = "😀"
println(emoji.length) // 2
println(emoji[0]) // high surrogate
println(emoji[1]) // low surrogate
所以:
String.length不是用户感知字符数。string[i]取到的是某个 UTF-16 code unit。- 处理 emoji、多语言文本、截断昵称时,不能简单按
Char切。
Java 对比:Java String.length() 也返回 UTF-16 code unit 数,问题相同。
字符转义¶
常用转义:
| 写法 | 含义 |
|---|---|
\t |
Tab |
\b |
Backspace |
\n |
换行 |
\r |
回车 |
\' |
单引号 |
\" |
双引号 |
\\ |
反斜杠 |
\$ |
美元符号 |
示例:
val newLine = '\n'
val dollar = '\$'
val backslash = '\\'
Unicode 字符:
val letterA = '\u0041'
字符操作¶
比较按 Unicode 数值:
println('a' < 'b') // true
println('A' == 'a') // false
检查和大小写转换:
val c = 'A'
println(c.isDigit()) // false
println(c.isUpperCase()) // true
println(c.lowercaseChar()) // a
字符加减整数:
val a = 'a'
println(a + 1) // b
println(a + 2) // c
这按 Unicode 值移动,不是自然语言字母表规则。不要用它处理本地化文本排序或大小写规则。
数字字符转换:
val digit = '7'
println(digit.digitToInt()) // 7
不确定是否为数字时:
val value = c.digitToIntOrNull()
无符号整数¶
Kotlin 提供无符号整数类型:
| 类型 | 位数 | 范围 |
|---|---|---|
UByte |
8 | 0 到 255 |
UShort |
16 | 0 到 65,535 |
UInt |
32 | 0 到 4,294,967,295 |
ULong |
64 | 0 到 18,446,744,073,709,551,615 |
字面量:
val a = 42u // UInt
val b = 1UL // ULong
val c: UByte = 1u
典型用途:
- 使用完整 bit 范围表示正值,例如颜色
AARRGGBB。 - 二进制协议、文件格式、网络协议。
- Native API 互操作。
示例:
data class Color(val value: UInt)
val yellow = Color(0xFFCC00CCu)
不要把无符号类型当作“非负业务整数”的默认选择。官方文档明确说明,不建议把它用于集合大小或索引。原因包括:
- 空集合的
lastIndex是-1,有符号整数能自然表达这类错误/边界值。 - 无符号和有符号整数不是子类型关系,互相转换会增加复杂度。
- 很多 Java/JVM API 仍使用有符号整数。
无符号数组¶
每个无符号类型都有数组类型:
UByteArrayUShortArrayUIntArrayULongArray
它们避免装箱,类似 IntArray 对 int[] 的作用。但官方文档说明,无符号数组及其操作仍是 Beta,需要 @ExperimentalUnsignedTypes opt-in:
@OptIn(ExperimentalUnsignedTypes::class)
fun utf8Bom(): UByteArray =
ubyteArrayOf(0xEFu, 0xBBu, 0xBFu)
公开 API 中暴露无符号数组要谨慎,因为 Beta 能力可能不兼容变化。
实践建议¶
- 默认整数用
Int,默认小数用Double。 - 需要跨 API、持久化或协议时,显式写清数字类型。
- 不依赖隐式数字转换,Kotlin 本来也不支持。
- 数字比较用
==,不要对 boxed 数字用===。 - 复杂布尔表达式加括号。
- 业务状态不要滥用
Boolean?。 Char不是完整 Unicode 字符,处理 emoji 和多语言文本时要按 code point 或文本库处理。- 无符号类型用于 bit/protocol/native,不用于普通非负业务值。
官方参考¶
- Numbers:https://kotlinlang.org/docs/numbers.html
- Booleans:https://kotlinlang.org/docs/booleans.html
- Characters:https://kotlinlang.org/docs/characters.html
- Unsigned integer types:https://kotlinlang.org/docs/unsigned-integer-types.html