数字
整数类型
Kotlin 提供了一组内置类型来表示数字。 对于整数,有四种不同大小和值区间的类型:
| 类型 | 大小 (位) | 最小值 | 最大值 |
|---|---|---|---|
Byte | 8 | -128 | 127 |
Short | 16 | -32768 | 32767 |
Int | 32 | -2,147,483,648 (-231) | 2,147,483,647 (231 - 1) |
Long | 64 | -9,223,372,036,854,775,808 (-263) | 9,223,372,036,854,775,807 (263 - 1) |
除了有符号整数类型之外,Kotlin 还提供了无符号整数类型。 由于无符号整数面向一组不同的用例,因此它们将在单独的部分中介绍。 关于无符号整数类型,请参见 unsigned-integer-types.md。
当你初始化一个变量时,如果没有显式类型指定,编译器会自动推断出从 Int 开始足以表示该值的最小区间类型。如果该值未超出 Int 的区间,则类型为 Int。如果超出该区间,则类型为 Long。要显式指定 Long 值,请在值后面附加后缀 L。要使用 Byte 或 Short 类型,请在声明中显式指定。显式类型指定会触发编译器检测值是否超出了指定类型的区间。
val one = 1 // Int
val threeBillion = 3000000000 // Long
val oneLong = 1L // Long
val oneByte: Byte = 1浮点类型
对于实数,Kotlin 提供了浮点类型 Float 和 Double,它们符合 IEEE 754 标准。Float 反映了 IEEE 754 的 单精度,而 Double 则反映了 双精度。
这些类型在大小上有所不同,并为不同精度的浮点数提供存储:
| 类型 | 大小 (位) | 有效位 | 指数位 | 十进制位数 |
|---|---|---|---|---|
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 is inferred
// Initializer type mismatch
val oneDouble = 1.0 // Double要为值显式指定 Float 类型,请添加后缀 f 或 F。如果以这种方式提供的值包含超过 7 位小数,则会被四舍五入:
val e = 2.7182818284 // Double
val eFloat = 2.7182818284f // Float, actual value is 2.7182817与某些其他语言不同,Kotlin 中没有数字的隐式加宽转换。例如,带有 Double 形参的函数只能对 Double 值调用,而不能对 Float、Int 或其他数值类型调用:
fun main() {
fun printDouble(x: Double) { print(x) }
val x = 1.0
val xInt = 1
val xFloat = 1.0f
printDouble(x)
printDouble(xInt)
// Argument type mismatch
printDouble(xFloat)
// Argument type mismatch
}要将数值转换为不同类型,请使用显式数字转换。
数字字面值常量
整数值有几种字面值常量:
- 十进制:
123 - 长整型,以大写
L结尾:123L - 十六进制:
0x0F - 二进制:
0b00001011
Kotlin 不支持八进制字面值。
Kotlin 还支持浮点数的常规表示法:
- 双精度浮点型(当小数部分不以字母结尾时的默认值):
123.5、123.5e10 - 单精度浮点型,以字母
f或F结尾:123.5f
你可以使用下划线使数字常量更具可读性:
val oneMillion = 1_000_000
val creditCardNumber = 1234_5678_9012_3456L
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 存储数字的方式可能会让你的代码行为反直觉,因为 JVM 默认会对小(字节大小)数字使用缓存。
JVM 将数字存储为原生类型:int、double 等。当你使用泛型或创建可空的数字引用(例如 Int?)时,数字会被装箱到 Java 类中,例如 Integer 或 Double。
JVM 对表示 −128 到 127 之间数字的 Integer 及其他对象应用了内存优化技术。所有对此类对象的可空引用都指向相同的缓存对象。例如,以下代码中的可空对象是引用相等的:
fun main() {
val a: Int = 100
val boxedA: Int? = a
val anotherBoxedA: Int? = a
println(boxedA === anotherBoxedA) // true
}对于超出此区间的数字,可空对象是不同的,但却是结构相等的:
fun main() {
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)时,请使用结构相等性检测来获得一致的结果。
显式数字转换
由于表示方式不同,数字类型_不是彼此的子类型_。因此,较小类型_不会_隐式转换为较大类型,反之亦然。例如,将 Byte 类型的值赋值给 Int 变量需要显式转换:
fun main() {
val byte: Byte = 1
// OK, literals are checked statically
val intAssignedByte: Int = byte
// Initializer type mismatch
val intConvertedByte: Int = byte.toInt()
println(intConvertedByte)
}所有数字类型都支持转换为其他类型:
toByte(): Byte(对于 Float 和 Double 已弃用)toShort(): ShorttoInt(): InttoLong(): LongtoFloat(): FloattoDouble(): Double
在许多情况下,不需要显式转换,因为类型是从上下文中推断出来的,并且算术操作符已重载以自动处理转换。例如:
fun main() {
val l = 1L + 3 // Long + Int => Long
println(l is Long) // true
}反对隐式转换的理由
Kotlin 不支持隐式转换,因为它们可能导致意外行为。
如果不同类型的数字被隐式转换,我们有时可能会悄无声息地失去相等性和标识。例如,设想如果 Int 是 Long 的子类型:
// Hypothetical code, does not actually compile:
val a: Int? = 1 // A boxed Int (java.lang.Integer)
val b: Long? = a // Implicit conversion yields a boxed Long (java.lang.Long)
print(b == a) // Prints "false" as Long.equals() checks not only the value but whether the other number is Long as well数字上的操作
Kotlin 支持对数字进行标准算术操作:+、-、*、/、%。它们被声明为相应类的成员:
fun main() {
println(1 + 2)
println(2_500_000_000L - 1L)
println(3.14 * 2.71)
println(10.0 / 3)
}你可以在自定义数字类中覆盖这些操作符。关于详细信息,请参见操作符重载。
整数除法
整数之间的除法总是返回一个整数。任何小数部分都会被丢弃。
fun main() {
val x = 5 / 2
println(x == 2.5)
// Operator '==' cannot be applied to 'Int' and 'Double'
println(x == 2)
// true
}这对于任意两个整数类型之间的除法都适用:
fun main() {
val x = 5L / 2
println (x == 2)
// Error, as Long (x) cannot be compared to Int (2)
println(x == 2L)
// true
}要返回带有小数部分的除法结果,请将其中一个实参显式转换为浮点类型:
fun main() {
val x = 5 / 2.toDouble()
println(x == 2.5)
}位操作
Kotlin 提供了一组整数上的_位操作_。它们直接在二进制级别上操作数字表示的位。位操作通过可以中缀形式调用的函数来表示。它们只能应用于 Int 和 Long:
fun main() {
val x = 1
val xShiftedLeft = (x shl 2)
println(xShiftedLeft)
// 4
val xAnd = x and 0x000FF000
println(xAnd)
// 0
}完整的位操作列表:
shl(bits)– 有符号左移shr(bits)– 有符号右移ushr(bits)– 无符号右移and(bits)– 按位 与or(bits)– 按位 或xor(bits)– 按位 异或inv()– 按位反转
浮点数比较
本节讨论的浮点数操作有:
- 相等性检测:
a == b和a != b - 比较操作符:
a < b、a > b、a <= b、a >= b - 区间实例化和区间检测:
a..b、x in a..b、x !in a..b
当操作数 a 和 b 被静态地知晓为 Float 或 Double 或它们的可空对应物(类型已声明或推断,或为智能转换的结果)时,对数字的操作以及它们形成的区间遵循 IEEE 754 浮点运算标准。
然而,为了支持泛型用例并提供全序,对于未被静态类型化为浮点数的操作数,行为会有所不同。例如,Any、Comparable<...> 或 Collection<T> 类型。在这种情况下,操作会使用 Float 和 Double 的 equals 和 compareTo 实现。结果是:
NaN被认为是与自身相等的NaN被认为大于任何其他元素,包括POSITIVE_INFINITY-0.0被认为小于0.0
以下示例显示了静态类型为浮点数的操作数(Double.NaN)与未静态类型化为浮点数的操作数(listOf(T))之间的行为差异。
fun main() {
// Operand statically typed as floating-point number
println(Double.NaN == Double.NaN) // false
// Operand NOT statically typed as floating-point number
// So NaN is equal to itself
println(listOf(Double.NaN) == listOf(Double.NaN)) // true
// Operand statically typed as floating-point number
println(0.0 == -0.0) // true
// Operand NOT statically typed as floating-point number
// So -0.0 is less than 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]
}